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 运行机制可视化器中,每次只放入一种声明。逐步比较准备与求值阶段,观察绑定状态如何对应最终输出。

亲自试一试逐步检查声明行为使用简单的 var、let、const 或函数例子,观察每条语句求值前后绑定的状态。