第4章:执行上下文 —— 逻辑与物理的桥梁
“执行上下文不是‘一个东西’,而是‘栈帧 + Context + 指针’的协作系统。”
—— 当你看到“逻辑抽象”与“物理实体”的映射,你就掌握了 JS 执行的“操作系统”。
执行上下文(逻辑层)—— 规范中的“蓝图”
在 ECMAScript 规范 中,执行上下文被定义为一个抽象结构:
text
ExecutionContext = {
ThisBinding: <this value>,
LexicalEnvironment: <a Lexical Environment>,
VariableEnvironment: <a Lexical Environment>
}这是一个逻辑模型,用于描述:
- 函数执行时的
this值 - 变量和函数的解析环境
- 作用域链的起点
但它不是 JavaScript 代码能直接访问的对象,也不是内存中的一个“大盒子”。
物理实现映射 —— V8 引擎的“施工图”
当 V8 引擎真正执行代码时,它必须用 C++ 数据结构来“建造”这个逻辑模型。
以下是逻辑概念到物理实现的精确映射:
| 逻辑概念 | 物理实现 | 存储位置 |
|---|---|---|
ThisBinding | 栈帧中的 receiver 字段 | 栈内存(Stack) |
LexicalEnvironment | 栈帧中的指针,指向 Context 对象 | 栈内存(存指针) |
VariableEnvironment | 栈帧中的另一个指针,指向 Context 对象 | 栈内存(存指针) |
变量绑定(如 x = 10) | 存储在 Context 对象的 variables_ 数组中 | 堆内存(Heap) |
| 作用域链 | 由 [[Environment]] 和 previous_ 指针连接的 Context 链表 | 堆内存(指针链) |
真实案例:函数执行时的完整物理图景
js
function greet(name) {
let message = "Hello, " + name;
console.log(message);
}
greet("Alice");当 greet 执行时,V8 构建了这样的物理结构:
1. 调用栈上创建栈帧
[调用栈]
┌─────────────────────────────┐ ← 栈顶
│ [greet 的栈帧] │
│ function: greet │
│ receiver: window │ ← ThisBinding
│ argv: ["Alice"] │
│ return_addr: 全局下一行 │
│ lex_env_ptr: → Context_greet │ ← LexicalEnvironment
│ var_env_ptr: → Context_greet │ ← VariableEnvironment
└─────────────────────────────┘2. 堆上创建 Context 对象
[堆内存]
Context_greet:
variables_[0]: name = "Alice"
variables_[1]: message = "Hello, Alice"
previous_: → 全局 Context3. 函数对象的 [[Environment]] 指向当前 Context
text
greet.[[Environment]] → Context_greet(用于支持可能的闭包)
关键结论:执行上下文是“协作系统”
执行上下文不是一个独立的实体,而是多个物理组件的协同工作:
1. 栈帧(Stack Frame) —— 提供“控制流”和 this
receiver字段承载ThisBindinglex_env_ptr和var_env_ptr指向Contextreturn_addr管理函数返回
2. Context 对象(堆上) —— 承载“变量状态”
variables_数组存储let/const/var变量previous_指针连接外层作用域- 是变量的“户籍所在地”
3. 指针链([[Environment]] 和 previous_) —— 构成“作用域链”
- 决定变量查找路径
- 支持闭包的“长期持有”能力
为什么理解这个映射至关重要?
1. 你不再“背概念”,而是“看本质”
- “执行上下文是一个包含 this 和环境的对象”
- “执行上下文是栈帧 + Context + 指针的协作系统”
后者能帮你诊断内存泄漏、优化性能、理解闭包。
2. 你理解了 let vs var 的底层差异
| 声明方式 | Context 中的处理 |
|---|---|
let / const | 在 LexicalEnvironment 指向的 Context 中创建 |
var | 在 VariableEnvironment 指向的 Context 中创建(通常是函数级) |
这就是为什么 var 有变量提升,而 let 有暂时性死区(TDZ)。
3. 你明白了“为什么闭包慢”
- 闭包需要创建
Context对象(堆分配,比栈慢) - 变量查找需要遍历
[[Environment]]链(比局部变量慢) - GC 需要管理
Context的生命周期(增加 GC 压力)