1. 变量提升的概念

变量提升(Hoisting)是 JavaScript 中变量和函数声明在代码执行前被移动到作用域顶部的行为。但实际提升的是声明本身,而非赋值操作。不同声明方式(varletconstfunction)表现不同。


2. var 的变量提升

现象

  • 声明提升:使用 var 声明的变量,其声明会被提升到作用域顶部。
  • 初始化为 undefined:变量在提升阶段会被赋予默认值 undefined
console.log(a); // undefined
var a = 10;

底层原理

  • 在代码执行前的编译阶段,JavaScript 引擎会扫描当前作用域中的 var 声明,并在变量对象(Variable Object, VO)中创建属性,初始化为 undefined
  • 执行阶段,赋值操作才会进行。

3. 函数提升

函数声明提升

  • 整体提升:函数声明会被完全提升,包括函数体。
foo(); // "Hello"
function foo() {
  console.log("Hello");
}

函数表达式提升

  • 视为变量提升:使用 var 声明的函数表达式,仅变量名被提升,函数体留在原地。
bar(); // TypeError: bar is not a function
var bar = function() {
  console.log("World");
};

4. let/const 的暂时性死区(TDZ)

现象

  • 声明前不可访问:在 let/const 声明前访问变量会抛出 ReferenceError
  • 存在提升吗?:声明本身会被提升,但未被初始化,形成暂时性死区(Temporal Dead Zone, TDZ)。
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 20;

底层原理

  • 在编译阶段,let/const 声明的变量会被记录在词法环境(Lexical Environment)中,但标记为 “未初始化”
  • 直到声明语句执行时,变量才会被初始化(let 初始化为未赋值,const 必须立即赋值)。

5. 底层原理:执行上下文与词法环境

执行上下文的两个阶段

  1. 创建阶段

    • 初始化作用域链。
    • 处理变量声明:
      • var:在变量对象中创建属性,初始化为 undefined
      • let/const:在词法环境中创建属性,标记为未初始化(TDZ)。
      • 函数声明:直接赋值函数体。
  2. 执行阶段

    • 逐行执行代码,处理赋值操作。

ES5 与 ES6 的环境模型

  • ES5(变量对象)var 和函数声明存储在变量对象中。
  • ES6(词法环境)let/const 使用词法环境,通过更严格的作用域规则管理。

6. 变量提升的优先级

  • 函数声明 > 变量声明:同名的函数声明会覆盖变量声明。
console.log(c); // ƒ c() {}
var c = 10;
function c() {}

7. 重复声明的影响

  • var:允许重复声明,后者覆盖前者。
  • let/const:同一作用域内不允许重复声明(SyntaxError)。

8. 块级作用域与变量提升

  • var 无视块级作用域varif/for 等块中声明的变量会提升到外层函数/全局作用域。
  • let/const 绑定块级作用域:仅在块内有效,且受 TDZ 限制。
{
  var d = 1;
  let e = 2;
}
console.log(d); // 1
console.log(e); // ReferenceError: e is not defined

9. 最佳实践

  1. 优先使用 let/const:避免变量提升的陷阱,利用块级作用域。
  2. 函数使用声明式:确保函数整体提升,避免表达式导致的意外。
  3. 变量声明集中在作用域顶部:提升代码可读性,符合引擎行为。

总结

  • var:提升且初始化为 undefined,函数作用域。
  • 函数声明:整体提升,优先于变量。
  • let/const:存在 TDZ,块级作用域。
  • 底层机制:通过执行上下文的创建与执行阶段管理变量,ES6 使用词法环境实现更严格的控制。

理解变量提升有助于编写可预测的代码,避免因作用域和声明顺序引发的错误。