1. 变量提升的概念
变量提升(Hoisting)是 JavaScript 中变量和函数声明在代码执行前被移动到作用域顶部的行为。但实际提升的是声明本身,而非赋值操作。不同声明方式(var
、let
、const
、function
)表现不同。
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. 底层原理:执行上下文与词法环境
执行上下文的两个阶段
-
创建阶段:
- 初始化作用域链。
- 处理变量声明:
var
:在变量对象中创建属性,初始化为undefined
。let
/const
:在词法环境中创建属性,标记为未初始化(TDZ)。- 函数声明:直接赋值函数体。
-
执行阶段:
- 逐行执行代码,处理赋值操作。
ES5 与 ES6 的环境模型
- ES5(变量对象):
var
和函数声明存储在变量对象中。 - ES6(词法环境):
let
/const
使用词法环境,通过更严格的作用域规则管理。
6. 变量提升的优先级
- 函数声明 > 变量声明:同名的函数声明会覆盖变量声明。
console.log(c); // ƒ c() {}
var c = 10;
function c() {}
7. 重复声明的影响
- var:允许重复声明,后者覆盖前者。
- let/const:同一作用域内不允许重复声明(SyntaxError)。
8. 块级作用域与变量提升
- var 无视块级作用域:
var
在if
/for
等块中声明的变量会提升到外层函数/全局作用域。 - let/const 绑定块级作用域:仅在块内有效,且受 TDZ 限制。
{
var d = 1;
let e = 2;
}
console.log(d); // 1
console.log(e); // ReferenceError: e is not defined
9. 最佳实践
- 优先使用
let
/const
:避免变量提升的陷阱,利用块级作用域。 - 函数使用声明式:确保函数整体提升,避免表达式导致的意外。
- 变量声明集中在作用域顶部:提升代码可读性,符合引擎行为。
总结
- var:提升且初始化为
undefined
,函数作用域。 - 函数声明:整体提升,优先于变量。
- let/const:存在 TDZ,块级作用域。
- 底层机制:通过执行上下文的创建与执行阶段管理变量,ES6 使用词法环境实现更严格的控制。
理解变量提升有助于编写可预测的代码,避免因作用域和声明顺序引发的错误。