Published on

let、const 与暂时性死区 (TDZ)

var 的经典行为与陷阱

在 ES6 之前,var 是唯一的变量声明方式,其行为由两个关键特性定义:

函数作用域 (Function Scope)

var 声明的变量,其作用域被限制在它所在的整个函数体内,而不是像许多其他语言那样的块级作用域 ({...})。这常常导致意料之外的结果。

经典陷阱:循环中的异步

for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    // 当回调执行时,循环早已结束,i 的值已变为 3
    console.log(i); // 会连续输出三次 3
  }, 10);
}

变量提升 (Hoisting)

var 声明的变量会被“提升”到其作用域的顶部,并被自动初始化为 undefined。这意味着在声明语句之前访问该变量不会报错,而是会得到 undefined

console.log(myVar); // 输出: undefined (而非 ReferenceError)
var myVar = "Hello, world!";

这种行为虽然是语言特性,但往往会掩盖因变量使用顺序不当而引发的潜在逻辑错误。

letconst:新一代的块级作用域声明

letconst 的出现,旨在提供一个更健壮、更符合开发者直觉的变量声明方案。

块级作用域 (Block Scope)

这是 letconst 最核心的改进。它们声明的变量,作用域被严格限制在离它最近的代码块 ({...}) 内。

解决循环陷阱

let 在循环中会为每次迭代创建一个新的词法环境,从而“记住”每次迭代时的值。

for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    // 每次回调引用的都是不同迭代中、独立的 i
    console.log(i); // 会依次输出 0, 1, 2
  }, 10);
}

暂时性死区 (Temporal Dead Zone - TDZ)

这是理解 letconst 提升行为的关键。

  • 提升依然存在:与 var 一样,letconst 的声明在概念上也会被提升到其所在块级作用域的顶部。
  • 关键区别:它们不会被自动初始化。从作用域顶部到该变量的声明语句之间的区域,被称为“暂时性死区”。
  • TDZ 的作用:在此区域内对该变量进行任何形式的访问(读或写),都会直接抛出一个 ReferenceError
设计目的

TDZ 的设计目的是 ES6 为 letconst 设计的一个统一的安全机制,其核心目标是:强制开发者“先声明,后使用”,从根本上杜绝因变量提升而导致的、在声明前就访问到 undefined 值的怪异行为,让代码更加稳健。

{
  // a 的暂时性死区开始
  // console.log(a); // ReferenceError: Cannot access 'a' before initialization
  
  let a = "I am safe."; // a 的暂时性死区结束
  console.log(a); // "I am safe."
}

const 的不变性契约

const 除了拥有 let 的所有特性(块级作用域、TDZ)外,还增加了一条规则:

  1. 必须在声明时初始化const myConst; 会直接导致 SyntaxError
  2. 绑定不可变:它确保的是变量标识符的绑定不可被重新赋值
绑定不可变

const 保证的是变量指向的那个内存地址不发生改变,但并不保证该地址上存储的数据内容不可变

// 声明一个常量对象
const user = { name: "Alice" };

// 这是允许的:修改对象内部的属性
user.name = "Bob";

// 这是不允许的:将 user 指向另一个对象,会抛出 TypeError
// user = { name: "Charlie" };

全面对比与工程实践建议

特性 (Feature)varletconst
作用域 (Scope)函数作用域块级作用域块级作用域
变量提升 (Hoisting)提升并初始化为 undefined提升但初始化提升但初始化
暂时性死区 (TDZ)
全局对象属性是(在顶层作用域时)
重复声明允许不允许不允许
必须初始化

最佳实践

  1. 默认使用 const:作为首选,它可以保证变量引用不变,向其他开发者清晰地传达该变量不应被重新赋值的意图,从而增强代码的可预测性。
  2. 仅在需要重新赋值时使用 let:当你明确知道一个变量的绑定需要改变时(例如循环计数器、可变状态等),才“降级”使用 let
  3. 在现代 JavaScript (ES6+) 中彻底避免使用 varletconst 在作用域和提升行为上提供了更安全、更符合逻辑的保证,已经没有理由在新的代码库中使用 var