2015 年,JavaScript 迎来了一次大版本更新——ES6(ECMAScript 2015),它不仅是语法的升级,更是 JS 这门语言从“老派”走向“现代”的分水岭。ES6 引入了许多新特性,但日常开发中最常用、最核心的莫过于四个:let 与 const、箭头函数、解构赋值、以及模板字符串等衍生能力。我们可以把这些特性想象成一次房屋大改造:var 像是一间杂物房,所有东西都堆在一起、容易串味;let 和 const 像带门的房间,东西归位、互不干扰;箭头函数像一把智能钥匙,不重新配锁、直接用主人的身份开门;解构赋值像一套打包收纳盒,一次取出一整套物品,省时省力。掌握它们,你就能从容应对现代 JS 代码,理解框架源码也不再吃力。

岗位关联

对 AI 从业者而言,ES6 是阅读现代前端框架(如 React、Vue)源码、编写 Node.js 工具链(如脚本、数据管道)的必备语法基础。不掌握它,你将寸步难行。

目录
  1. 可视化演示
  2. let 与块级作用域:给变量一个专属房间
  3. const 与常量绑定:只许看不许换的相框
  4. 箭头函数:简洁到极致的函数写法
  5. this 绑定的秘密:箭头函数中的 this 是“包养”的
  6. 数组解构赋值:一次拆箱一整套
  7. 对象解构赋值:按名牌找东西
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

本节配有交互动效(进入视口后自动加载,约 3KB)。

ES6核心:作用域、this与解构 let/const 声明的变量仅在所在代码块内有效,var 声明的变量在整个函数内可见 箭头函数不绑定自己的 this,继承外层作用域的 this,解决了回调中 this 指向问题 解构赋值按位置(数组)或属性名(对象)匹配,支持默认值和嵌套解构 let/const管作用域,箭头函数管this,解构管取值

let 与块级作用域:给变量一个专属房间

想象你家的杂物房,以前所有东西都堆在里面,不分区域。var 声明的变量就像杂物房里的东西,放在哪个角落似乎不重要,甚至 JavaScript 引擎会在代码执行前先把它们“搬”到房间中央——这就是变量提升。不管你在哪里声明 var,它都会被提到作用域顶部,这容易导致意想不到的 undefined。更糟的是,var 不存在“房间”的概念,一个 if 块、一个 for 循环里声明的变量,在块外面依然能访问到。

let 的出现彻底改变了这个局面。如果用 let 声明变量,它就像被装进一个带有房门的专属房间中,这个房间就是块级作用域。块级作用域指的是 {} 包裹的范围,比如 if、for、while 的花括号。在房间内部可以自由使用变量,但走出房门,变量就不可见了。这从根本上解决了 var 的污染问题。

let 还有一个重要的特性叫暂时性死区(TDZ,即 Temporal Dead Zone)。简单说,在 let 声明之前访问该变量,会直接报错——“暂时性死区”这个名字很有趣,因为它让变量在声明前的这段时间里,连“存在”都算不上。这就像房间还没建好时,你试图去房间里拿东西,只能撞到一堵墙。

1. 声明前访问报错:在 let x 执行前的代码中访问 x,引擎会抛出 ReferenceError,这是 TDZ 的直接体现。

ReferenceError: Cannot access ’x’ before initialization\text{ReferenceError: Cannot access 'x' before initialization}

2. 声明后正常访问:声明语句执行完毕后,x 进入可访问状态,读取到的是初始化后的值。

x=10⇒可读可用x = 10 \Rightarrow \text{可读可用}

在循环中使用 let 更是如鱼得水。for (let i = 0; i < 5; i++) 里的 i 在每次迭代时都是一个新的绑定,这解决了 var 在循环闭包中的经典问题。以前的代码里,循环内定义函数,函数里引用循环变量,最终拿到的永远是循环结束后的值;现在用 let,每次迭代的 i 都被“锁定”了。这正是“给变量一个专属房间”的威力——每间房间都有自己的居民,互不串门。

声明前访问→ReferenceError⏟暂时性死区声明后访问→正常值⏟let 生效\underbrace{\text{声明前访问} \rightarrow \text{ReferenceError}}_{\text{暂时性死区}} \quad \underbrace{\text{声明后访问} \rightarrow \text{正常值}}_{\text{let 生效}}
js5 行
if (true) {  let x = 10;  console.log(x); // 10,房间里可以访问}console.log(x); // ReferenceError: x is not defined,走出房间就找不到了

const 与常量绑定:只许看不许换的相框

const 是 let 的姊妹,它声明的是一个“常量绑定”。你可以把它想象成一个固定在墙上的相框:相框的位置不能换,不能说你今天把它挂到客厅、明天移到卧室;但相框里的照片是可以更换的(这个特性到后面讲引用类型时再展开)。const 基本规则是:声明时必须赋初值,之后不能重新赋值。

我们来仔细看一下引用类型的情况。const 禁止的是“重新绑定”,即不能把变量名指向另一个对象;但对象内部的属性是可以修改的。这就像刚说的相框,相框(变量名)不能换成另一个,但照片(内容)随时可以换。

为了解决“内容也能冻结”的需求,可以使用 Object.freeze(),它让对象变为真正不可变——不能新增、删除、修改属性。这相当于把相框连照片一起用玻璃封死了。在真实项目中,Object.freeze 常用于定义配置常量,防止代码在某个角落意外修改全局配置。

那到底什么时候用 let、什么时候用 const?社区约定俗成的准则是:优先用 const,除非你确定需要重新赋值才用 let。因为 const 传达的语义是“这个值不应该被变”,这对代码的可读性和可维护性都有好处。如果你看一个变量从头到尾只被赋值一次,那立即就能判断它是一个常量;如果看到 let,则会注意它在哪些地方被修改了。

js2 行
const PI = 3.14159;PI = 3; // TypeError: Assignment to constant variable.
js3 行
const user = { name: 'Alice' };user.name = 'Bob'; // 合法,修改属性不改变引用user = { name: 'Charlie' }; // TypeError,重新赋值报错

箭头函数:简洁到极致的函数写法

箭头函数(Arrow Function)是 ES6 中最具辨识度的语法之一。如果用一句话概括,它就是“把 function 和 {} 都藏起来的函数速写”。回到搬家的比喻中,普通函数像一辆手动挡汽车——你得踩离合、挂档、松手刹,步骤齐全;箭头函数则像自动挡,一脚油门就走,简单直接。

基本的箭头函数语法是这样的:

箭头函数有几个核心简写规则:

1. 当参数只有一个时,括号可以省略:x => x * 2。

2. 当函数体只有一个表达式时,花括号和 return 都可以省略,并且会自动返回这个表达式的值——这就是隐式返回。

3. 没有参数时,括号必须保留:() => Date.now()。

如果函数体有多个语句,就需要写花括号和显式 return。这个规则很好记:能省略的前提是函数体“一眼就能看穿”,多语句时就别硬缩写了。

箭头函数与普通函数还有一个重要区别:它没有自己的 arguments 对象。在普通函数中,arguments 是类数组对象,包含所有传入的参数;而箭头函数中,你需要用剩余参数(rest parameters)来实现类似功能:

js4 行
// 普通函数function add(a, b) { return a + b; }// 箭头函数const add = (a, b) => a + b;
js2 行
const double = x => x * 2;const random = () => Math.random();
js2 行
const logAll = (...args) => console.log(args);logAll(1, 2, 3); // [1, 2, 3]

this 绑定的秘密:箭头函数中的 this 是“包养”的

this 是 JavaScript 中最令人困惑的概念之一。在普通函数中,this 取决于调用方式——谁调用了函数,this 就指向谁,这被称为动态绑定。这就好比一个员工,被哪个部门叫去开会,他的胸牌就换成哪个部门的名字;你永远猜不到下一次他会被叫到哪去。

箭头函数则完全改变了这套规则:它没有自己的 this。箭头函数中的 this 是在定义函数时从外层词法作用域“继承”来的,且一旦确定,就无法改变。

这段对比非常经典。普通函数在 setTimeout 中被调用时,this 变成了全局对象,所以读不到 name;而箭头函数捕获了它定义时所在的 greet 方法中的 this,于是 this.name 正常输出。用一句话总结:箭头函数让 this 变成了“包养的”,不再看调用者的脸色,而是看定义时的环境。

因此,箭头函数不能用作构造函数。因为它没有自己的 this,意味着无法绑定新创建的对象,new 一个箭头函数会直接报错。同理,call()、apply()、bind() 方法也无法改变箭头函数的 this 指向——你试图用这些方法“强行换锁”,但箭头函数根本不吃这一套。

在实际开发中,这个特性在事件回调、定时器、异步请求场景中尤为实用——这些场景下你往往希望 this 稳定地指向外层对象,而不是因调用方式而变化。

js17 行
const obj = {  name: 'Alice',  greet: function() {    setTimeout(function() {      console.log('Hello, ' + this.name); // undefined,this 指向 window    }, 100);  }}; const obj2 = {  name: 'Bob',  greet: function() {    setTimeout(() => {      console.log('Hello, ' + this.name); // 'Hello, Bob',this 继承自 greet    }, 100);  }};

数组解构赋值:一次拆箱一整套

解构赋值(Destructuring)就像搬家时的打包拆箱。想象你搬家时用一个个收纳箱把厨房用品装好,到了新家按箱子上的标签逐一拆开,省时省力。解构赋值做的正是这件事:把数组或对象中的值“拆开”,一次性赋给多个变量,不再需要一个一个取。

先看数组解构的基础用法:

这条代码相当于把数组的 colors[0]、colors[1]、colors[2] 依次赋给 first、second、third。解构的精髓在于它可以灵活跳过不需要的元素:

更强大的是,解构支持默认值。如果数组元素是 undefined,默认值就会生效——这就像拆箱时发现某样东西用旧报纸裹着但其实是易碎品,你提前准备了备用方案:

注意,默认值只有在严格等于 undefined 时才生效。如果该位置是 null,默认值不会触发。因为 null 是一个“有意的空值”,而 undefined 才是“未赋值相框”。

数组解构最实用的场景之一是交换变量。以前交换两个变量需要一个临时变量,就像搬家时先把沙发搬到走廊、再把茶几搬进来,最后把沙发搬进屋;现在可以一步到位:

这行代码可读性极强,一眼就看出“交换 x 和 y”。

1. 位置匹配:a 取第 0 项,即 10。解构本质是“按位置取数”。

a←arr[0]=10a \leftarrow arr[0] = 10

2. 继续匹配:b 取第 1 项,即 20。

b←arr[1]=20b \leftarrow arr[1] = 20

3. 默认值触发:c 对应第 2 项,但数组只有两项,取到 undefined,所以默认值 30 生效。

c←arr[2]≡undefined⇒c=30c \leftarrow arr[2] \equiv undefined \Rightarrow c = 30

js5 行
const colors = ['red', 'green', 'blue'];const [first, second, third] = colors;console.log(first);  // 'red'console.log(second); // 'green'console.log(third);  // 'blue'
js2 行
const [, , thirdColor] = colors;console.log(thirdColor); // 'blue'
js3 行
const [a, b, c = 100] = [1, undefined, 3];console.log(b); // undefinedconsole.log(c); // 3,因为 c 的位置是 3,覆盖了默认值 100
js3 行
let x = 1, y = 2;[x, y] = [y, x];console.log(x, y); // 2, 1
js1 行
const [a, b, c = 30] = [10, 20];

对象解构赋值:按名牌找东西

对象解构和数组解构逻辑相通,但对象没有“位置”概念,靠的是属性名匹配。回到拆箱比喻中,数组解构是按“第几件”拿东西,对象解构则是按“名牌”拿东西——每个收纳箱上都贴着专属标签,你按标签找物,准确高效。

对象解构的变量名默认是属性名。如果你想换一个变量名,可以使用重命名语法:

这里 name 是匹配的键名,userName 是新变量名。注意系统约定:左边是“从对象里取哪个键”,右边是“赋给哪个变量”。这和数组解构“左边是变量、右边是值”的直觉略有不同,容易混淆。

对象解构支持嵌套:

这就像拆开一个大的收纳箱,里面还有一个小收纳盒,再拆一层。

对象解构最常用的场景之一是函数参数解构——直接在函数签名中把传入对象的属性提取出来,让函数体变得清爽:

配合默认值可以应对参数缺失的情况:

这样设计的好处是:即使调用时不传参数,也不会报错。默认参数从 undefined 兜底,同时 = {} 给整个参数也设了默认空对象,双重保险。

至此,六大分支全部讲完:let 给变量装了隔断墙,const 守护常量绑定,箭头函数将函数“瘦身”并固定 this,数组解构按序取物、对象解构按名牌找物。这四把钥匙,能帮你打开现代 JavaScript 世界的绝大部分房门。

js4 行
const user = { name: 'Alice', age: 25 };const { name, age } = user;console.log(name); // 'Alice'console.log(age);  // 25
js2 行
const { name: userName, age: userAge } = user;console.log(userName); // 'Alice'
js3 行
const person = { name: 'Bob', address: { city: 'Beijing', zip: '100000' } };const { address: { city } } = person;console.log(city); // 'Beijing'
js4 行
function greet({ name, age }) {  return `Hi, I'm ${name}, ${age} years old.`;}greet({ name: 'Alice', age: 25 }); // "Hi, I'm Alice, 25 years old."
js4 行
function greet({ name = 'Anonymous', age = 0 } = {}) {  return `Hi, I'm ${name}, ${age} years old.`;}greet(); // "Hi, I'm Anonymous, 0 years old."

AI 应用场景

场景一:用 let/const 重构 Node.js 数据管道脚本

阶段:数据预处理 / 脚本编写

解决的问题:在 AI 项目中,我们经常需要写 Node.js 脚本处理数据,如读取 CSV、清洗字段、转换格式。老式 var 声明的变量在复杂循环中很容易产生意外覆盖,尤其当脚本很长时,变量污染会让调试极其痛苦。使用 let 和 const 能将变量锁定在块级作用域内,大幅降低“变量被莫名修改”的概率。

具体案例:假设你写了一个数据处理脚本,遍历多个 JSON 文件并提取目标字段。每个文件数据量很大,循环内临时变量多。以前用 var 时,因变量提升和函数作用域,经常会遇到“上一个文件的残留数据进入下一个文件”的诡异 bug。用 const 存储文件路径、let 存储动态计数后,这类问题彻底消失。

场景二:箭头函数 + 解构赋值读取模型推理结果

阶段:模型推理 / 结果后处理

解决的问题:深度学习模型的输出往往是一个嵌套的对象或数组,如 { predictions: [{ label: 'cat', score: 0.9 }], meta: {...} }。传统取值需要 const label = result.predictions[0].label,样板代码多、易出错。解构赋值能在一条语句中提取多个字段,箭头函数让回调逻辑更简洁。

具体案例:前端实时展示图像分类结果,后端返回 { predictions: [{ label, score }], latency, model_version }。只需一行 const { predictions: [{ label, score }], latency } = response 即可取出所需字段,再配合 setTimeout 刷新 UI,无需额外工具函数。

场景三:前端 AI 可视化仪表盘的状态管理

阶段:前端展示 / 交互逻辑

解决的问题:AI 可视化大屏往往涉及大量异步请求、状态更新、图表重绘。this 指向混乱、异步回调中读取不到最新状态,是前端开发者的常见痛点。箭头函数固化 this 指代外层组件,配合对象解构提取配置项,可让状态管理模块变得简洁而稳定。

具体案例:React 组件中,useEffect 里请求模型状态接口,setTimeout 回调中更新图表。若不用箭头函数,this.setState 可能因 this 丢失而报错;用箭头函数后,this 始终指向组件实例,再加解构赋值从接口结果中提取关键指标,代码行数直接减半。

Python 演示

python60 行
def demonstrate_const_like_behavior():    """    模拟 JS const 的引用语义:可以修改内部元素,但不可重新绑定。    用 tuple 和 list 对应 const 数组/对象的不同层面。    """    print("=== 模拟 const 绑定 ===")    # list 内部可变,类似 const obj = { ... } 中的属性修改    const_like = [1, 2, 3]    const_like[0] = 100    print(f"list 内部可变: {const_like}")  # [100, 2, 3]     # tuple 不可重新绑定,类似 const 禁止重新赋值    tuple_fixed = (10, 20)    try:        tuple_fixed = (99, 99)  # 在 JS 里这会 TypeError    except TypeError as e:        print(f"元组不可重新绑定: {e}")    return const_like def arrow_function_like_lambda():    """用 Python lambda 演示箭头函数的简洁写法。"""    print("\n=== 箭头函数与 lambda 类比 ===")    double = lambda x: x * 2    print(double(5))  # 10    # 多个参数    add = lambda a, b: a + b    print(add(3, 4))  # 7 def destructuring_like_unpacking():    """用 Python 的解包(unpacking)演示 JS 解构赋值。"""    print("\n=== 解构赋值类比 ===")    arr = [1, 2, 3]    a, b, c = arr    print(a, b, c)  # 1 2 3     # 交换变量    x, y = 10, 20    x, y = y, x    print(x, y)  # 20 10     # 对象解构 -> dict unpack    user = {"name": "Alice", "age": 25}    name, age = user["name"], user["age"]    print(name, age)  # Alice 25 def block_scope_compare():    """对比 Python 的块级作用域感与 JS let/const 差异(概念演示)。"""    print("\n=== 块级作用域类比(Python 无块级作用域) ===")    # 在 Python 中,for 循环内定义的变量在循环后仍可访问    # 这与 var 相似,但与 let/const 不同    for i in range(3):        temp = i * 2    print(f"循环后 temp 仍可访问: {temp}")  # 4    print("JS 中 let 会把 temp 限制在块内") if __name__ == "__main__":    demonstrate_const_like_behavior()    arrow_function_like_lambda()    destructuring_like_unpacking()    block_scope_compare()
目的:用 Python 中相似的概念来类比演示 ES6 四大新特性的核心语义,帮助 JS 初学者用已知 Python 知识快速建立直觉。
  1. tuple_fixed = (99, 99)尝试给元组重新绑定,会触发 TypeError,类比 JS 中给 const 重新赋值的报错。
  2. double = lambda x: x * 2用匿名函数对应箭头函数的“极简写法”。
  3. a, b, c = arr用元组解包对应 JS 数组解构赋值。
  4. x, y = y, x一行代码交换变量,与 JS 的 [x, y] = [y, x] 逻辑一致。
  5. temp = i * 2演示 Python 中循环变量泄漏,与 JS 的 var 行为类似,反衬 let 的块级作用域价值。
  6. name, age = user["name"], user["age"]手动提取 dict 中的字段,对应 JS 对象解构的“按名牌取物”。

常见误区

误区很多初学者以为 `const user = { name: 'Alice' }` 之后,`user.name = 'Bob'` 会报错,认为 `const` 声明的对象是“死的”,任何修改都不允许。

正解const 锁定的只是绑定关系,即变量名指向的内存地址不可变。对于对象和数组这类引用类型,const 只保证“不能重新指向新对象”,但对象内部的属性、数组的元素都可以修改。如果希望对象完全不可变,需要使用 Object.freeze(),但即便冻结后,嵌套对象也需递归冻结才能真正不可变。

为何常见:因为 const 名字里有“常量”二字,人们容易把它和“不可变数据”画等号,忽略了 JS 中“引用类型”的特殊性。

误区有人认为箭头函数就只是把 `function` 换成 `=>`,写的短一点而已,行为上和普通函数没有区别。

正解箭头函数和普通函数有本质差异:一是它没有自己的 this,继承外层词法作用域的 this;二是它不能作为构造函数,不能使用 new;三是没有 arguments 对象;四是用 call/apply/bind 无法改变它的 this。这些差异在异步回调、事件绑定场景中至关重要。

为何常见:从外观上看,箭头函数确实只是简写,且多数入门教程先展示“简化写法”再讲差异,容易让人忽视深层的绑定机制差异。

误区有经验的开发者可能觉得 `var` 用着顺手,`let` 增加了报错反而麻烦,比如暂时性死区、块级作用域限制,感觉“太严了”。

正解let 的“严格”正是它的价值所在。变量提升和函数作用域导致的隐性 bug 极难排查,而块级作用域和 TDZ 能在开发阶段就暴露问题,而不是留到运行时或生产环境才炸雷。严格不是限制,而是保护。

为何常见:人天生对限制有抵触,尤其老开发者习惯了 var 的宽松。但从工程化、团队协作角度看,let/const 的约束能提升代码的可预测性和可维护性。

误区很多初学者认为 `const [a = 10] = [null]` 中 `a` 会等于 10,因为“有默认值就不用管实际值了”;或 `const { b = 1 } = { b: 0 }` 中 `b` 应该等于 1。

正解解构的默认值只在属性/元素严格为 undefined 时触发。如果是一般的空值如 null、0、'',它会正常赋值,默认值不会生效。这一点和函数参数的默认值规则一致(虽然函数参数默认值有个别差异,但核心思想相同)。

为何常见:因为“默认值”的直觉是“没有就用这个”,而 JS 区分了 undefined 与 null,初学者往往把这两者混为一谈。

误区有人这样理解:箭头函数里的 `this` 指向“定义箭头函数时所在的那个对象”。比如在 `obj.method()` 中定义箭头函数,`this` 就指向 `obj`。

正解更准确的说法是:箭头函数的 this 继承定义时所在作用域的 this,也就是词法作用域(lexical scope)的 this,而不是“所在对象”。它本质上是在定义时就捕获了外层函数的 this 值并固定下来。注意作用域和对象的区分——如果箭头函数直接定义在全局,它的 this 就是全局对象,与它“长在哪里”无关(除非它在另一个函数内部)。

为何常见:因为“定义时所在”这个描述在大多数场景下碰巧匹配,比如对象方法里的箭头函数确实指向对象,但深入边界场景(如嵌套函数、全局箭头函数)就会暴露逻辑破绽。

面试题

Q1. 下列哪个声明方式在 ES6 中推荐用于声明一个“不会重新赋值”的变量?

单选题 · easy

  • A.var
  • B.let
  • C.const← 正确答案
  • D.static

答案:C。const 用于声明不可重新绑定的变量,语义上表达“此值不变”;let 用于声明可重新赋值的变量;var 是旧语法,有提升等问题;static 是类的方法修饰符,与变量声明无关。

Q2. let 和 var 的最大区别是什么?

单选题 · easy

  • A.let 比 var 速度更快
  • B.let 支持块级作用域← 正确答案
  • C.let 可以重复声明
  • D.let 不能声明数字

答案:B。let 引入块级作用域,变量只在 {} 内可见;var 是函数作用域,没有“块”概念,所以容易产生变量泄漏。性能差异不值得作为选型依据,let 禁止重复声明,var 允许重复声明。

Q3. 以下箭头函数写法中,哪一个是合法的单参数简洁写法?

单选题 · easy

  • A.x => { return x * 2; }
  • B.(x) => x * 2
  • C.x => x * 2← 正确答案
  • D.x, y => x * 2

答案:C。当只有一个参数时,箭头函数可以省略参数括号,所以 x => x * 2 合法。A 也是合法的,但它是带块体的写法;B 合法但不是最简洁;D 多参数必须加括号,写成 (x, y) => x * 2 更规范,直接写 x, y => x * 2 语法错误。

Q4. 关于箭头函数中的 this,以下哪个描述是正确的?

单选题 · mid

  • A.this 指向调用箭头函数的对象
  • B.this 由 call() 方法动态指定
  • C.this 继承定义时外层作用域的 this← 正确答案
  • D.this 指向全局对象,无法改变

答案:C。箭头函数没有自己的 this,它捕获的是定义时外层词法作用域的 this,并且后续无法改变。A 是普通函数动态绑定的行为;B 错在 call() 无法改变箭头函数 this;D 忽略了它可以继承外层 this 且不一定全局。

Q5. 执行 const [a, b = 10] = [1, undefined] 后,b 的值是?

单选题 · mid

  • A.undefined
  • B.10← 正确答案
  • C.null
  • D.抛出错误

答案:B。解构到 b 时,数组中对应位置是 undefined,触发默认值,所以 b = 10。如果该位置是 null 或 0,则 b 会取到这些值而不是默认值。

Q6. 以下哪些行为是箭头函数做不到的?

多选题 · mid

  • A.作为构造函数使用 new← 正确答案
  • B.使用 arguments 对象← 正确答案
  • C.通过 bind() 改变 this← 正确答案
  • D.作为数组 map 的回调

答案:ABC。箭头函数不能用作构造函数(A 错)、没有自己的 arguments 对象(B 错)、bind() 无法改变 this(C 错),但它可以正常作为 map、filter 等回调使用(D 对)。

Q7. 以下哪个代码片段正确实现了“交换 a 和 b 两个变量的值”?

单选题 · mid

  • A.let a = 1, b = 2; a = b; b = a;
  • B.let a = 1, b = 2; [a, b] = [b, a];← 正确答案
  • C.let a = 1, b = 2; const temp = a; a = b; b = temp;
  • D.let a = 1, b = 2; { let temp = a; a = b; b = temp; }

答案:B。[a, b] = [b, a] 是 ES6 的优雅交换写法。A 会让 a 和 b 都变成 2,不符合预期;C 和 D 虽然正确,但 C 的方案在 ES6 之前常用,D 借助块级作用域实现也正确,但本题问“正确实现”,B、C、D 都可以实现,但 B 是最简洁的 ES6 方式。注意题目要求选择正确片段,这里应选最符合 ES6 风格的 B。实际上 C 和 D 也正确,但 B 是唯一用 ES6 解构语法的。综合而言 B 最贴切。

Q8. 关于 let 与 const 的差别,以下哪种说法是正确的?

多选题 · hard

  • A.let 声明的变量可以重新赋值← 正确答案
  • B.const 声明的对象属性不可修改
  • C.let 存在暂时性死区← 正确答案
  • D.const 声明的变量必须立即初始化← 正确答案

答案:ACD。A 对,let 允许重新赋值;B 错,const 只锁定绑定关系,对象内部属性可以修改;C 对,let 在声明前访问会触发 ReferenceError;D 对,const 声明时必须赋初值,否则报语法错误。

Q9. 以下代码的输出是什么?

单选题 · hard

  • A.0 1 2
  • B.3 3 3← 正确答案
  • C.0 0 0
  • D.抛出错误

答案:B。var 是函数作用域,循环结束后 i 变为 3;setTimeout 回调在循环完成后执行,读取到的是同一变量 i 的最新值,所以输出 3 3 3。若将 var 改为 let,每次迭代会创建独立绑定,输出才是 0 1 2。这正是 let 在循环中的关键价值。

Q10. 对象解构中 const { name: userName, age = 18 } = { name: 'Alice' } 的结果是?

多选题 · hard

  • A.userName 的值是 'Alice'← 正确答案
  • B.name 变量也被创建了
  • C.age 的值是 18← 正确答案
  • D.userName 的值是 undefined

答案:AC。name: userName 表示读取对象的 name 属性并赋给 userName,所以 userName 是 'Alice',但 name 变量本身不会创建。age 在对象中不存在,得到 undefined,触发默认值 18。因此 A 和 C 正确。