JavaScript 声明指南
JavaScript 变量提升:声明、初始化与暂时性死区
用实用模型理解为什么 var、let、const 和函数声明在源码行执行前表现不同。
“变量提升”是多种语言规则的简写。把它理解为“JavaScript 会把代码移动到顶部”,往往只会制造更多误解。
更好的模型是把作用域的准备与语句的求值分开。不同声明形式会在这个过程的不同节点创建绑定、初始化值。
一句话回答
- var 在赋值语句求值前就创建并初始化为 undefined。
- let 和 const 的绑定会提前存在,但在声明求值前读取会因尚未初始化而抛错。
- 函数声明会在外围代码实例化时完成初始化;赋给变量的函数表达式则不会。
1. var 在赋值前就已经可用
第一次 console.log 运行时,message 这个名字已经存在,但它的赋值语句还没有被求值。因此观察到的是 undefined,而不是后面的字符串。
这通常被称为 var 提升。关键细节是它已经被初始化为 undefined,这与不存在的绑定或尚未初始化的词法绑定不同。
赋值语句运行前,message 已初始化为 undefined
console.log(message); // undefined
var message = "ready";
console.log(message); // "ready"2. let 与 const 有尚未初始化的阶段
词法声明会为所在作用域创建绑定,但在声明语句被求值前,JavaScript 不允许读取它。这个阶段通常称为暂时性死区。
结果是 ReferenceError,而不是 undefined。这个差异能避免把尚未初始化的值误当成一个合理结果。
在声明前读取 status 会抛出错误
console.log(status); // ReferenceError
let status = "ready";3. 函数声明与函数表达式在这里并不等价
名为 announce 的函数声明会在调用表达式求值前完成初始化。相对地,run 是一个 var 绑定;在赋值取得函数表达式之前,它的值仍是 undefined。
两种写法没有绝对优劣,重点是要知道它们的初始化规则不同。
函数声明可以提前调用;var 函数表达式不可以
announce();
function announce() {
console.log("declared");
}
run(); // TypeError: run is not a function
var run = function () {
console.log("expressed");
};4. 预测轨迹,而不是记一句口号
面对每个名字,分别问两个问题:它的绑定何时被创建?它何时获得可用的值?这样可以预测常见例子,而不必假装源码行会被物理移动。
在 JS 运行机制可视化器中,每次只放入一种声明。逐步比较准备与求值阶段,观察绑定状态如何对应最终输出。