文章目录
JavaScript 变量提升(Hoisting)是编译阶段的特殊机制,其核心原理如下:
一、变量提升底层机制
1️⃣ 编译阶段处理
- 在代码执行前,JS 引擎会先解析代码并创建执行上下文
- 对所有声明语句(
var
、function
)进行预处理
2️⃣ 存储空间划分
// 编译阶段会创建变量环境(VariableEnvironment):
{
a: undefined, // var 声明
b: function(){...} // function 声明
}
二、提升规则
声明类型 | 提升行为 | 示例 |
---|---|---|
var 变量 |
声明提升到作用域顶部,值初始化为undefined |
console.log(a); var a=1; → undefined |
function 声明 |
整个函数体提升 | 可先调用再声明 |
let/const |
存在暂时性死区(TDZ) | 提前访问会报错 |
暂时性死区:暂时性死区(Temporal Dead Zone)是 ES6 中 let/const 声明的特性,指:从进入作用域到变量声明语句执行前的区域,此时访问变量会触发 ReferenceError。
三、执行阶段表现
// 原始代码
console.log(a);
var a = 1;
// 实际执行逻辑等同于:
var a; // 提升声明
console.log(a); // undefined
a = 1; // 原地赋值
四、变量提升特殊注意点
- 函数优先级:函数声明提升优先于变量声明
console.log(foo); // 输出函数体,而非undefined
var foo = 1;
function foo() {}
- 重复声明覆盖(如下代码案例):
function b() {
console.log("第一次调用");
}
function b() {
console.log("第二次调用");
} // 后者覆盖前者
- 块级作用域影响(ES6+):
{
let a = 1;
var b = 2;
}
console.log(b); // 2
console.log(a); // ReferenceError
五、变量提升最佳实践建议
- 使用
let/const
替代var
避免意外提升 - 函数优先用表达式写法:
const fn = () => {}
- 避免在同一作用域重复声明同名标识符
💡 现代开发中应通过 ESLint 规则 (如
no-use-before-define
) 来规避变量提升带来的潜在问题。
六、var
、let
和 const
的核心区别对比
特性 | var |
let |
const |
---|---|---|---|
作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
重复声明 | ✅ 允许 | ❌ 禁止 | ❌ 禁止 |
变量提升 | ✅ 提升且初始化为undefined |
⚠️ 提升但存在 TDZ | ⚠️ 提升但存在 TDZ |
初始化要求 | 可后赋值 | 可后赋值 | ❗ 必须立即赋值 |
全局对象属性 | ✅ (如 window.a ) |
❌ | ❌ |
重新赋值 | ✅ 允许 | ✅ 允许 | ❌ 禁止(对象属性可修改) |
七、关键差异详解
1. 作用域范围
// var:函数作用域
function testVar() {
if (true) {
var a = 1;
}
console.log(a); // 1(泄露到函数作用域)
}
// let/const:块级作用域
function testLet() {
if (true) {
let b = 2;
const c = 3;
}
console.log(b); // ReferenceError
console.log(c); // ReferenceError
}
2. 变量提升与暂时性死区(TDZ)
// var 的变量提升
console.log(x); // undefined
var x = 5;
// let/const 的 TDZ
console.log(y); // ❌ ReferenceError
let y = 10;
3. 重复声明
var name = "Alice";
var name = "Bob"; // ✅ 允许
let age = 25;
let age = 30; // ❌ SyntaxError
const PI = 3.14;
const PI = 3.14159; // ❌ SyntaxError
4. const 的特殊性
const user = { name: "Tom" };
user.name = "Jerry"; // ✅ 允许(修改对象属性)
user = {}; // ❌ TypeError(禁止重新赋值)
const arr = [1, 2];
arr.push(3); // ✅ 允许(修改数组内容)
arr = [4, 5]; // ❌ TypeError
八、最佳实践建议
优先使用
const
声明后不需修改的变量(如配置项、数学常量)需要重新赋值时用
let
循环计数器、状态变量等避免使用
var
防止变量泄露和意外覆盖(ES6+项目)注意循环陷阱
// var 的陷阱 for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i)); // 输出 3, 3, 3 } // let 的正确用法 for (let j = 0; j < 3; j++) { setTimeout(() => console.log(j)); // 输出 0, 1, 2 }
九、常见问题
TDZ 的表现
let/const
声明前访问会触发错误,而var
是undefined
。全局污染问题
var globalVar = 1; console.log(window.globalVar); // 1 let moduleVar = 2; console.log(window.moduleVar); // undefined
函数提升优先级
函数声明优先于var
变量提升:console.log(typeof func); // "function" var func = "变量"; function func() {}