let 声明的范围

let 跟 var 的作用差不多,最明显的区别是,let 声明的范围是块作用域,而 var 声明的范围是函数作用域。

  1. if (true) {
  2. var name = 'Matt';
  3. console.log(name); // Matt
  4. }
  5. console.log(name); // Matt
  1. if (true) {
  2. let age = 26;
  3. console.log(age); // 26
  4. }
  5. console.log(age); // ReferenceError: age 没有定义

在这里,age 变量之所以不能在 if 块外部被引用,是因为它的作用域仅限于该块内部。块作用域是函数作用域的子集,因此适用于 var 的作用域限制同样也适用于 let。

冗余声明报错

同一个块作用域中出现冗余声明,会报错:

  1. var name;
  2. var name;
  3. let age;
  4. let age; // SyntaxError;标识符 age 已经声明过了

只要同一个块作用域中没有重复声明,就不会报错:

  1. var name = 'Nicholas';
  2. console.log(name); // 'Nicholas'
  3. if (true) {
  4. var name = 'Matt';
  5. console.log(name); // 'Matt'
  6. }
  7. let age = 30;
  8. console.log(age); // 30
  9. if (true) {
  10. let age = 26;
  11. console.log(age); // 26
  12. }

声明冗余报错不会因混用 let 和 var 而受影响。因为声明的同一变量,只是指出变量在相关作用域如何存在。

  1. var name;
  2. let name; // SyntaxError
  3. let age;
  4. var age; // SyntaxError

暂时性死区

let 声明的变量不会在作用域中被提升。

  1. // name 会被提升
  2. console.log(name); // Matt
  3. var name = 'Matt';
  4. // age 不会被提升
  5. console.log(age); // ReferenceError:age 没有定义
  6. let age = 26;

for 循环中的 let 声明

在 let 出现之前,for 循环定义的迭代变量会渗透到循环体外部:

  1. for (var i = 0; i < 5; ++i) {
  2. // 循环逻辑
  3. }
  4. console.log(i); // 5

改成使用 let 之后,这个问题就消失了,因为迭代变量的作用域仅限于 for 循环块内部:

  1. for (let i = 0; i < 5; ++i) {
  2. // 循环逻辑
  3. }
  4. console.log(i); // ReferenceError: i 没有定义

对迭代变量的奇特声明和修改

在使用 var 的时候,最常见的问题就是对迭代变量的奇特声明和修改:

  1. for (var i = 0; i < 5; ++i) {
  2. setTimeout(() => console.log(i), 0)
  3. }
  4. // 你可能以为会输出 0、1、2、3、4
  5. // 实际上会输出 5、5、5、5、5

之所以会这样,是因为在退出循环时,迭代变量保存的是导致循环退出的值:5。在之后执行超时逻辑时,所有的 i 都是同一个变量,因而输出的都是5。
而在使用 let 声明迭代变量时,JavaScript 引擎在后台会为每个迭代循环声明一个新的迭代变量。每个 setTimeout 引用的都是不同的变量实例,所以 console.log 输出的是循环执行过程中每个迭代变量的值。

  1. for (let i = 0; i < 5; ++i) {
  2. setTimeout(() => console.log(i), 0)
  3. }
  4. // 会输出 0、1、2、3、4

这种每次迭代声明一个独立变量实例的行为适用于所有风格的 for 循环,包括 for-in 和 for-of循环。