1. 变量提升的原理与核心机制
1.1 概念与核心要点
变量提升到底是什么?在 JavaScript 的执行模型里,变量提升指的是在代码实际执行之前,声明部分会被移动到当前作用域的顶部,以便后续代码可以访问到它。需要明确的是,提升的对象是声明本身,而赋值或初始化仍然按原始顺序执行。这也是为什么在使用 var 声明的变量时,首次访问得到的通常是 undefined,而不是抛错。
从宏观角度看,提升分为两类:变量声明提升与函数声明的提升。函数声明往往在作用域内被完整提升,允许在“声明前”调用;而普通变量声明仅提升了名称,初始化的时序仍受代码顺序影响。这一区别是理解后续示例的关键。
// var 的提升:声明提升,初始化跟随在后
console.log(a); // undefined
var a = 42;// 函数声明的提升
fn(); // "hello"
function fn() { return "hello"; }// 函数表达式的提升不同步
fnExpr(); // TypeError: fnExpr is not a function
var fnExpr = function() { return "hello"; };// let/const 的提升与 TDZ
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 7;
在上面的代码中可以看到,仅仅是变量名的声明参与提升,而像赋值、初始化这样的操作仍然按原有顺序执行。这也是理解后续关于 let/const 的行为差异的基础。
1.2 对不同声明类型中的差异
不同的声明关键字在提升中的表现有本质差异。var声明属于“声明提升并初始为 undefined”的模式;function声明会被提升成完整的函数定义;let/const具有TDZ(暂时性死区),在初始化前访问会抛出 ReferenceError,这使得它们看起来像没有被提升,但实际上它们的声明已经存在于作用域中。
理解这些差异对于诊断实际代码中的执行顺序至关重要,尤其是在复杂的作用域和函数组合中。下面的综合示例有助于对比它们的实际行为。
// var 的提升与 undefined
console.log(v); // undefined
var v = 100;// function 声明的提升
greet(); // "Hi"
function greet() { return "Hi"; }// 函数表达式的提升
// 注意:函数表达式的变量名会被提升,但赋值不会
bar(); // TypeError: bar is not a function
var bar = function() { return "bar"; };// let/const 的 TDZ 行为
console.log(t); // ReferenceError
let t = 2;
2. 触发时机:何时发生变量提升
2.1 解析阶段的提升与准备
在 JavaScript 的执行过程中,作用域的建立和变量环境的初始化发生在解析阶段,这时引擎会将 声明记录在当前作用域的变量对象中,为后续的执行做准备。提升的核心在于作用域中的声明被“看到”并可在后续代码中访问,即使它的赋值还没有到达。

此外,执行上下文的创建阶段会初始化 变量对象、作用域链、this,不过真正的值赋予发生在执行阶段。理解这两步的分离,是理解提升为何与初始化分离的关键。
// 解析阶段:变量 x 的声明已经存在于作用域中
console.log(x); // undefined (还未赋值)
var x = 10;
2.2 执行阶段中的实际赋值与访问
进入执行阶段后,变量的初始化和赋值按顺序进行。对于 var,这意味着变量在进入执行阶段时已经存在于作用域内,初始值为 undefined,随后遇到赋值语句才得到实际值。对于 let/const,变量处于 TDZ,只有真正执行到初始化时才能使用,否则会抛出错误。
下面的示例更直观地呈现了两者的不同:
// var 的初始化发生在赋值语句处
console.log(n); // undefined
var n = 99;// let/const 的 TDZ 体现
console.log(m); // ReferenceError: Cannot access 'm' before initialization
let m = 21;
3. 常见误区与纠正
3.1 误区:变量提升等同于初始化
常见的误解是“提升意味着变量会提前被赋予初始值”。事实上,提升只涉及声明,而初始化仍然在原位置执行,这也是为何使用 var 时经常看到 console.log 输出 undefined 而非抛错。
将这一点区分清楚,有助于避免在代码中出现难以追踪的顺序性错误。
console.log(p); // undefined
var p = 5;
console.log(p); // 5
3.2 误区:let/const 也会像 var 那样被提升
现实并非如此。let/const 存在 TDZ,在变量真正初始化前访问它们会抛出 ReferenceError,这与 var 的行为完全不同。理解 TDZ 是避免运行时错误的关键。
示例对比有助于澄清:
console.log(q); // ReferenceError
let q = 3;
3.3 误区:函数表达式的提升与函数声明相同
很多人误以为“变量名提升后,调用总是可用”。实际情况是:函数表达式的提升仅提升变量名,赋值在执行到该语句时才完成,因此在赋值之前调用会得到 TypeError。这与函数声明的提升形成明显对比。
下面的对比清晰呈现两者的不同之处:
bar(); // TypeError: bar is not a function
var bar = function() { return 'baz'; };bar2(); // "baz"
function bar2() { return 'baz'; }


