变量提升 就是:JavaScript 在运行代码之前,会先把代码里的变量声明 和函数声明 "提前"放到作用域的顶部,但赋值操作留在原地。
生活化比喻
想象你去一家公司上班:
- 公司人事部(编译器)先花 5 分钟把全公司员工的工位牌(变量名)先摆好
- 然后你才开始正式工作(执行代码)
所以你可以在公司正式开业前就看到工位牌,但工位上有没有电脑(值),那得看正式上班后有没有发。
1. var 的提升:最容易理解
ini
console.log(a); // undefined(不会报错,但也不等于 10)
var a = 10;
console.log(a); // 10
这段代码在 JS 眼里,相当于这样执行:
javascript
var a; // 声明被"提升"到顶部,此时 a = undefined
console.log(a); // undefined
a = 10; // 赋值留在原地
console.log(a); // 10
所以:
var a = 10;在 JS 内部被拆成了两步- 第一步
var a;被提到顶部,初始值是undefined - 第二步
a = 10;留在原位置
2. 函数提升:整个函数都会被提前
scss
sayHi(); // "Hi!" 可以正常调用
function sayHi() {
console.log("Hi!");
}
因为函数声明会被整体提升,所以即使写在后面,前面也能调用。
3. 但函数表达式不会整体提升
javascript
sayHello(); // ❌ TypeError: sayHello is not a function
var sayHello = function() {
console.log("Hello!");
};
原因:这里的 sayHello 是 变量 ,不是函数声明。它只会按 var 的规则提升:
scss
var sayHello; // 提升到顶部,值是 undefined
sayHello(); // undefined() → 报错
sayHello = function() { ... }; // 赋值留在原地
4. let 和 const:也有提升,但不能提前用
ini
console.log(b); // ❌ ReferenceError: Cannot access 'b' before initialization
let b = 20;
let 和 const 声明也会被提升,但 JS 给它们设了一个"暂时性死区"(TDZ):在声明之前不能访问,访问就报错。
可以理解为:工位牌摆好了,但上面盖着红布,在正式揭牌之前你看一眼就报错。
javascript
{
// TDZ 开始
// console.log(a); // ❌ ReferenceError: Cannot access 'a' before initialization
let a; // ← 声明在这里,TDZ 结束
console.log(a); // ✅ undefined(不是报错!)
a = 10; // 赋值
console.log(a); // 10
}
关键点: let a; 声明之后,变量已经存在,只是值为 undefined,不会报 TDZ 错误。
5. 引擎实际执行流程
以 V8 为例,真实流程大致是:
sql
源代码
↓
词法分析 + 语法分析 → 抽象语法树 AST
↓
作用域分析(Scope Analysis):确定有哪些变量、函数、作用域链
↓
编译成字节码(Bytecode,Ignition 解释器)
↓
执行字节码
↓
热点代码被优化编译成机器码(TurboFan JIT)
所以**"变量提升"真正发生的位置**,是上面第二步和第三步之间------也就是 AST 解析完成、作用域建立的时候。
6. 简单总结表
arduino
console.log(x); // 这一行之前有没有声明?
| 声明方式 | 声明之前访问会怎样 | 原因 |
|---|---|---|
var x = 1 |
undefined |
声明提升,初始化为 undefined |
let x = 1 |
报错 ReferenceError |
声明提升,但处于暂时性死区 |
const x = 1 |
报错 ReferenceError |
同上 |
function x(){} |
可以正常调用 | 整个函数被提升 |
7. 一个容易混淆的例子
ini
var x = 1;
function foo() {
console.log(x); // undefined,不是 1!
var x = 2;
console.log(x); // 2
}
foo();
为什么第一次打印不是 1?
因为函数内部的 var x = 2 发生了函数作用域内的提升:
Javascript
javascript
function foo() {
var x; // 提升到函数顶部
console.log(x); // undefined
x = 2; // 赋值
console.log(x); // 2
}
这里提升的是函数自己的
x,它"遮住"了外部的x。这叫做变量遮蔽(Shadowing) 。
最终一句话
变量提升就是:声明先被 JS 放到顶部,但赋值不动;var 会提前变成 undefined,let/const 会提前让你报错,函数声明可以提前调用。