再探执行上下文
目标:理解执行上下文不仅是“执行环境”,更是变量管理、作用域链构建、this 绑定、调用栈协作的核心机制。
一、执行上下文的本质:抽象的执行环境
官方定义(ECMAScript 规范):
执行上下文是用于跟踪代码执行的抽象结构。
但它到底“抽象”在哪里?我们来看它的三个核心组件:
| 组件 | 作用 |
|---|---|
| 1. 词法环境(Lexical Environment) | 处理作用域和标识符解析,决定变量从哪里来 |
| 2. 变量环境(Variable Environment) | 处理变量声明(var、function)的绑定 |
| 3. This 绑定(This Binding) | 确定当前上下文中 this 的值 |
🔥 执行上下文 = { 词法环境, 变量环境, This 绑定 }
二、执行上下文的生命周期:创建阶段 vs 执行阶段
每个执行上下文都会经历两个阶段:
1. 创建阶段(Creation Phase)
在函数被调用但代码尚未执行时发生。
步骤:
| 步骤 | 说明 |
|---|---|
📌 1. 确定 This Binding | 根据调用方式设置 this |
| 📌 2. 创建词法环境 | 用于 let、const、function(块级) |
| 📌 3. 创建变量环境 | 用于 var、函数声明(函数级) |
| 📌 4. 初始化环境记录 | 为参数、变量分配内存,var 初始化为 undefined |
例子:
js
function foo(x) {
var a = 1;
let b = 2;
function bar() {}
}
foo(10);创建阶段:
This Binding:根据调用方式确定(如window)VariableEnvironment:{ a: undefined, bar: function }LexicalEnvironment:{ b: uninitialized }(暂时性死区)- 参数:
x: 10
注意:a 是 undefined(因为 var 提升),b 是 uninitialized(暂时性死区)。
2. 执行阶段(Execution Phase)
代码逐行执行,变量被赋值,函数被调用。
a = 1→ 更新VariableEnvironmentb = 2→ 更新LexicalEnvironment- 调用
bar()→ 创建新的执行上下文,压入调用栈
三、词法环境 vs 变量环境:为什么有两个?
这是最容易混淆的点。我们来对比:
| 维度 | 词法环境(Lexical Environment) | 变量环境(Variable Environment) |
|---|---|---|
| 用途 | 处理 let、const、function(块级) | 处理 var、函数声明(函数级) |
| 提升行为 | 不提升,有暂时性死区(TDZ) | var 提升,初始化为 undefined |
| 作用域 | 块级作用域({}) | 函数级作用域 |
| 历史原因 | ES6 引入 let/const 后新增 | ES5 及之前的主要变量环境 |
例子:
js
{
console.log(a); // undefined (var 提升)
console.log(b); // ReferenceError (TDZ)
var a = 1;
let b = 2;
}a在VariableEnvironment中,已初始化为undefinedb在LexicalEnvironment中,处于暂时性死区
变量环境是为了兼容 var 的提升行为。
词法环境是为了支持块级作用域和 let/const。
四、执行上下文与调用栈:运行时的“工作台队列”
1. 调用栈(Call Stack)是什么?
- 一个后进先出(LIFO)的栈结构。
- 用于管理函数的调用顺序。
- 每个栈帧(stack frame)就是一个执行上下文。
2. 执行上下文如何进入调用栈?
js
function foo() {
bar();
}
function bar() {
console.log("hello");
}
foo();调用过程:
- 全局执行上下文 创建 → 压入栈
- 调用
foo()→ 创建foo的执行上下文 → 压入栈 - 调用
bar()→ 创建bar的执行上下文 → 压入栈 bar执行完毕 → 弹出foo继续 → 执行完毕 → 弹出- 回到全局上下文
调用栈变化:
[Global]
→ [Global, foo]
→ [Global, foo, bar]
→ [Global, foo]
→ [Global]调用栈里存的是执行上下文的实例,不是函数本身,也不是内存地址。
五、执行上下文与词法作用域:如何协作?
这是最核心的联动!
关键桥梁:[[Environment]]
- 每个函数都有一个内部属性
[[Environment]],指向它定义时的词法环境。 - 这个值在函数创建时由词法作用域决定。
执行时如何查找变量?
- 在当前执行上下文的 词法环境 中查找。
- 如果找不到,通过
[[Environment]]指针,找到外层词法环境。 - 继续向上,直到全局环境。
js
let x = 1;
function outer() {
let y = 2;
function inner() {
let z = 3;
console.log(x, y, z);
}
inner();
}inner 执行时:
- 查找
x:本地没有 →[[Environment]]→outer的词法环境 → 没有 → 全局 → 找到x = 1
执行上下文提供“当前环境”,词法作用域提供“查找路径”。
六、This Binding:执行上下文的动态部分
this 是执行上下文的一部分,但它在创建阶段才确定,取决于调用方式。
| 调用方式 | this 绑定 |
|---|---|
obj.fn() | obj |
fn() | 全局对象(非严格) / undefined(严格) |
new Fn() | 新创建的对象 |
fn.call(obj) | obj |
js
const obj = {
name: "Alice",
greet() {
console.log(this.name);
}
};
const fn = obj.greet;
fn(); // undefined(严格模式)← 因为 this 是全局或 undefinedgreet的[[Environment]]指向obj的环境(词法作用域)。但
this是在执行时根据调用方式绑定的(执行上下文)。[[Environment]]是静态的(词法作用域),this是动态的(调用方式)。
七、执行上下文的类型
| 类型 | 创建时机 | 特点 |
|---|---|---|
| 全局执行上下文 | 页面加载时创建 | 只有一个,this 指向全局对象 |
| 函数执行上下文 | 函数被调用时创建 | 每次调用都创建新的上下文 |
| 块级执行上下文 | let/const 在块中声明时 | ES6 引入,用于块级作用域 |
块级上下文不是函数调用产生的,而是由 {} 块触发。
八、图解:执行上下文的完整协作流程
源代码
↓
词法分析 → 确定词法作用域结构
↓
函数调用
↓
创建执行上下文:
- 确定 this
- 创建 VariableEnvironment(var, function)
- 创建 LexicalEnvironment(let, const)
- 设置 [[Environment]] ← 由词法作用域决定
↓
压入调用栈
↓
执行代码:
- 变量赋值 → 更新环境
- 函数调用 → 递归创建新上下文
- 变量查找 → 作用域链遍历
↓
执行完毕 → 弹出栈 → 等待 GC九、一句话总结执行上下文
执行上下文是 JavaScript 引擎为每次函数调用(或代码块执行)搭建的“运行时工作台”——它管理变量、确定 this、链接作用域, 并通过调用栈协同工作,使得代码能够有序、可预测地执行。
结语
你现在已经掌握了 JavaScript 运行时的“操作系统”:
- 词法作用域是“设计图”。
- 执行上下文是“工作台”。
- 调用栈是“任务队列”。
- 闭包是“带着设计图到处跑的工人”。