从执行上下文理解JavaScript变量提升

在学习 JavaScript 的过程中,我们可能会遇到一个经典问题:
|
|
很多人第一反应是:
|
|
但实际运行结果却是:
|
|
为什么会出现这种情况?
理解这个问题的关键在于 JavaScript 的 执行上下文(Execution Context) 和 变量提升(Hoisting) 机制。
JavaScript 是如何执行代码的
JavaScript 代码并不是从上到下一行一行直接执行的,而是大致分为两个阶段:
flowchart TD
A[编译阶段 创建执行上下文] --> B[执行阶段 逐行执行代码]
flowchart TD
A[编译阶段 创建执行上下文] --> B[执行阶段 逐行执行代码]在编译阶段,JavaScript 引擎会:
- 创建执行上下文
- 创建变量对象 VO
- 扫描
var - 扫描
function - 建立作用域链
- 确定
this
flowchart TD
A[编译阶段开始] --> B[创建执行上下文 Execution Context]
B --> C[创建变量对象 VO]
C --> D[扫描变量声明 var]
D --> E[变量加入 VO
初始值 undefined]
C --> F[扫描函数声明 function]
F --> G[函数整体加入 VO]
G --> H[建立作用域链 Scope Chain]
H --> I[确定 this 指向]
I --> J[编译阶段完成]
flowchart TD
A[编译阶段开始] --> B[创建执行上下文 Execution Context]
B --> C[创建变量对象 VO]
C --> D[扫描变量声明 var]
D --> E[变量加入 VO初始值 undefined] C --> F[扫描函数声明 function] F --> G[函数整体加入 VO] G --> H[建立作用域链 Scope Chain] H --> I[确定 this 指向] I --> J[编译阶段完成]
示例:
|
|
编译阶段后:
|
|
在编译阶段会发生变量提升(Hoisting)。主流浏览器和 Node.js 使用的 JavaScript 引擎是V8。
什么是执行上下文 (Execution Context)
简单的一句话总结:执行上下文 = JavaScript 代码执行时的环境。
它决定了代码在运行时 变量如何查找、函数如何调用、this 指向什么。
JavaScript 中有三种执行上下文:全局执行上下文(Global Execution Context)、函数执行上下文(Function Execution Context)和eval执行上下文。
执行上下文的结构
每个执行上下文内部通常包含三个核心部分:
|
|
其中最重要的是LexicalEnvironment。它内部包含:EnvironmentRecord用于存储变量:
|
|
变量提升是如何发生的
回到最开始的代码:
|
|
当test()被调用时,JavaScript 会创建新的执行上下文test Execution Context。
在编译阶段,引擎会扫描变量声明:
|
|
并在环境记录中创建变量:
|
|
此时执行上下文变成:
|
|
代码真实执行顺序
执行阶段,JavaScript 会逐行执行代码:
flowchart TD
A[进入执行阶段] --> B["执行 console.log(a)"]
B --> C[查找变量 a]
C --> D[在 VO 中找到 a]
D --> E[当前值 undefined]
E --> F[输出 undefined]
F --> G[执行 a = 2]
G --> H[更新 VO 中的 a]
H --> I[执行结束]
flowchart TD
A[进入执行阶段] --> B["执行 console.log(a)"]
B --> C[查找变量 a]
C --> D[在 VO 中找到 a]
D --> E[当前值 undefined]
E --> F[输出 undefined]
F --> G[执行 a = 2]
G --> H[更新 VO 中的 a]
H --> I[执行结束]为什么不是输出 1
代码中实际上存在两个a:
|
|
JavaScript 查找变量遵循 作用域链规则:
flowchart TD
A[当前作用域] --> B[外层作用域]
B --> C[全局作用域]
flowchart TD
A[当前作用域] --> B[外层作用域]
B --> C[全局作用域]当执行:
|
|
- 先查找函数作用域
- 找到了变量
a - 但值是
undefined - 因此不会再查找全局
a
最终输出undefined。
如果使用 let 会发生什么
如果改成:
|
|
运行会报错:
|
|
原因是let存在暂时性死区(Temporal Dead Zone,TDZ)。在let或const声明的变量 从作用域开始到变量声明之前的区域,在这段时间内访问变量会直接抛出 ReferenceError。变量在声明之前不可访问。
模拟 JavaScript 引擎编译
下面用 JavaScript 模拟一个简单的编译器行为:
|
|
测试编译:
|
|
输出如下:

写一个简单的执行器:
|
|
执行:
|
|

总结
理解变量提升的关键是理解执行上下文的创建过程。
核心要点:
- JavaScript 代码执行前会创建执行上下文
- 在编译阶段会扫描变量声明
var声明的变量会被初始化为undefined- 局部变量会遮蔽外部变量
let和const存在暂时性死区
相关内容
支付宝
微信