- 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!";
这种行为虽然是语言特性,但往往会掩盖因变量使用顺序不当而引发的潜在逻辑错误。
let
与 const
:新一代的块级作用域声明
let
和 const
的出现,旨在提供一个更健壮、更符合开发者直觉的变量声明方案。
块级作用域 (Block Scope)
这是 let
和 const
最核心的改进。它们声明的变量,作用域被严格限制在离它最近的代码块 ({...}
) 内。
解决循环陷阱
let
在循环中会为每次迭代创建一个新的词法环境,从而“记住”每次迭代时的值。
for (let i = 0; i < 3; i++) {
setTimeout(function() {
// 每次回调引用的都是不同迭代中、独立的 i
console.log(i); // 会依次输出 0, 1, 2
}, 10);
}
暂时性死区 (Temporal Dead Zone - TDZ)
这是理解 let
和 const
提升行为的关键。
- 提升依然存在:与
var
一样,let
和const
的声明在概念上也会被提升到其所在块级作用域的顶部。 - 关键区别:它们不会被自动初始化。从作用域顶部到该变量的声明语句之间的区域,被称为“暂时性死区”。
- TDZ 的作用:在此区域内对该变量进行任何形式的访问(读或写),都会直接抛出一个
ReferenceError
。
设计目的
TDZ 的设计目的是 ES6 为 let
和 const
设计的一个统一的安全机制,其核心目标是:强制开发者“先声明,后使用”,从根本上杜绝因变量提升而导致的、在声明前就访问到 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)外,还增加了一条规则:
- 必须在声明时初始化:
const myConst;
会直接导致SyntaxError
。 - 绑定不可变:它确保的是变量标识符的绑定不可被重新赋值。
绑定不可变
const
保证的是变量指向的那个内存地址不发生改变,但并不保证该地址上存储的数据内容不可变。
// 声明一个常量对象
const user = { name: "Alice" };
// 这是允许的:修改对象内部的属性
user.name = "Bob";
// 这是不允许的:将 user 指向另一个对象,会抛出 TypeError
// user = { name: "Charlie" };
全面对比与工程实践建议
特性 (Feature) | var | let | const |
---|---|---|---|
作用域 (Scope) | 函数作用域 | 块级作用域 | 块级作用域 |
变量提升 (Hoisting) | 提升并初始化为 undefined | 提升但不初始化 | 提升但不初始化 |
暂时性死区 (TDZ) | 无 | 有 | 有 |
全局对象属性 | 是(在顶层作用域时) | 否 | 否 |
重复声明 | 允许 | 不允许 | 不允许 |
必须初始化 | 否 | 否 | 是 |
最佳实践:
- 默认使用
const
:作为首选,它可以保证变量引用不变,向其他开发者清晰地传达该变量不应被重新赋值的意图,从而增强代码的可预测性。 - 仅在需要重新赋值时使用
let
:当你明确知道一个变量的绑定需要改变时(例如循环计数器、可变状态等),才“降级”使用let
。 - 在现代 JavaScript (ES6+) 中彻底避免使用
var
:let
和const
在作用域和提升行为上提供了更安全、更符合逻辑的保证,已经没有理由在新的代码库中使用var
。