Skip to content

再探执行上下文

目标:理解执行上下文不仅是“执行环境”,更是变量管理、作用域链构建、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. 创建词法环境用于 letconstfunction(块级)
📌 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

注意:aundefined(因为 var 提升),buninitialized(暂时性死区)。

2. 执行阶段(Execution Phase)

代码逐行执行,变量被赋值,函数被调用。

  • a = 1 → 更新 VariableEnvironment
  • b = 2 → 更新 LexicalEnvironment
  • 调用 bar() → 创建新的执行上下文,压入调用栈

三、词法环境 vs 变量环境:为什么有两个?

这是最容易混淆的点。我们来对比:

维度词法环境(Lexical Environment)变量环境(Variable Environment)
用途处理 letconstfunction(块级)处理 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;
}
  • aVariableEnvironment 中,已初始化为 undefined
  • bLexicalEnvironment 中,处于暂时性死区

变量环境是为了兼容 var 的提升行为。
词法环境是为了支持块级作用域和 let/const

四、执行上下文与调用栈:运行时的“工作台队列”

1. 调用栈(Call Stack)是什么?

  • 一个后进先出(LIFO)的栈结构
  • 用于管理函数的调用顺序。
  • 每个栈帧(stack frame)就是一个执行上下文

2. 执行上下文如何进入调用栈?

js
function foo() {
  bar();
}
function bar() {
  console.log("hello");
}
foo();

调用过程:

  1. 全局执行上下文 创建 → 压入栈
  2. 调用 foo() → 创建 foo 的执行上下文 → 压入栈
  3. 调用 bar() → 创建 bar 的执行上下文 → 压入栈
  4. bar 执行完毕 → 弹出
  5. foo 继续 → 执行完毕 → 弹出
  6. 回到全局上下文
调用栈变化:
[Global]
→ [Global, foo]
→ [Global, foo, bar]
→ [Global, foo]
→ [Global]

调用栈里存的是执行上下文的实例,不是函数本身,也不是内存地址。

五、执行上下文与词法作用域:如何协作?

这是最核心的联动!

关键桥梁:[[Environment]]

  • 每个函数都有一个内部属性 [[Environment]],指向它定义时的词法环境
  • 这个值在函数创建时由词法作用域决定。

执行时如何查找变量?

  1. 在当前执行上下文的 词法环境 中查找。
  2. 如果找不到,通过 [[Environment]] 指针,找到外层词法环境。
  3. 继续向上,直到全局环境。
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 是全局或 undefined
  • greet[[Environment]] 指向 obj 的环境(词法作用域)。

  • this 是在执行时根据调用方式绑定的(执行上下文)。

  • [[Environment]] 是静态的(词法作用域),this 是动态的(调用方式)

七、执行上下文的类型

类型创建时机特点
全局执行上下文页面加载时创建只有一个,this 指向全局对象
函数执行上下文函数被调用时创建每次调用都创建新的上下文
块级执行上下文let/const 在块中声明时ES6 引入,用于块级作用域

块级上下文不是函数调用产生的,而是由 {} 块触发。

八、图解:执行上下文的完整协作流程

源代码

词法分析 → 确定词法作用域结构

函数调用

创建执行上下文:
  - 确定 this
  - 创建 VariableEnvironment(var, function)
  - 创建 LexicalEnvironment(let, const)
  - 设置 [[Environment]] ← 由词法作用域决定

压入调用栈

执行代码:
  - 变量赋值 → 更新环境
  - 函数调用 → 递归创建新上下文
  - 变量查找 → 作用域链遍历

执行完毕 → 弹出栈 → 等待 GC

九、一句话总结执行上下文

执行上下文是 JavaScript 引擎为每次函数调用(或代码块执行)搭建的“运行时工作台”——它管理变量、确定 this、链接作用域, 并通过调用栈协同工作,使得代码能够有序、可预测地执行。

结语

你现在已经掌握了 JavaScript 运行时的“操作系统”:

  • 词法作用域是“设计图”。
  • 执行上下文是“工作台”。
  • 调用栈是“任务队列”。
  • 闭包是“带着设计图到处跑的工人”。