← [ BACK_TO_NOTES ]

ECMAScript执行上下文

作用域、调用栈、提升与执行上下文之间的运行时关系。

#concept #growing #javascript #ecmascript

[!info] related notes

ECMAScript执行上下文

一句话定义

执行上下文是 JavaScript 引擎在执行代码时创建的运行环境,包含变量绑定、this 指向和作用域链,每次函数调用都会生成一个新的执行上下文。

三个容易混淆的词

  • 作用域: 变量在代码里按定义位置形成的可访问边界
  • 执行上下文: 一段代码执行时实际创建的运行环境
  • 调用栈: 多个执行上下文按调用顺序堆叠起来的结构

常见执行上下文类型

  • 全局执行上下文
  • 函数执行上下文
  • 模块执行上下文

eval 也能形成特殊上下文,但现代代码很少把它当主线理解。

一个函数执行时大致会发生什么

创建阶段

  • 确定外层词法环境引用
  • 处理参数
  • 建立声明绑定
  • 准备 this 与 new.target 等运行时信息

执行阶段

  • 自上而下运行可执行语句
  • 在需要时沿作用域链查找标识符

调用栈可视化

function outer() {
  const a = 1
  inner()
}

function inner() {
  const b = 2
  console.log(a + b)  // ReferenceError: a is not defined
}

outer()

调用栈变化:

1. [GlobalContext]
2. [GlobalContext, outerContext]
3. [GlobalContext, outerContext, innerContext]
4. [GlobalContext, outerContext]          // ReferenceError 从 inner 向外传播
5. [GlobalContext]                        // outer 也因未捕获异常退出

标识符沿词法环境查找,不沿调用栈查找。本例 inner 定义在 outer 外部,所以看不到 outer 的 a;它抛错后也不会正常继续后面的语句。

提升与暂时性死区

提升不是代码真的被搬到顶部,而是声明绑定在上下文创建阶段就已经处理。

console.log(a)  // undefined(var 已创建,未赋值)
var a = 10

console.log(b)  // ReferenceError(TDZ)
let b = 20
  • function 声明通常最早可用
  • var 会被创建并初始化为 undefined
  • let / const 也会被登记,但在初始化前处于暂时性死区(TDZ)

TDZ 的范围

{
  // TDZ 开始
  console.log(x)  // ReferenceError
  // TDZ 结束
  let x = 1
  console.log(x)  // 1
}

对块内的 let/const 绑定,TDZ 从进入该词法环境持续到初始化完成;初始化表达式中读取自身也会抛错,例如 let x = x。

this 绑定

this 的值在执行上下文创建阶段确定,取决于函数的调用方式:

const obj = {
  name: 'foo',
  greet() {
    console.log(this.name)
  }
}

obj.greet()          // "foo"(隐式绑定)
const fn = obj.greet
fn()                 // 严格函数中 this 为 undefined,读取 this.name 抛 TypeError;非严格时读取全局 name

obj.greet.call({ name: 'bar' })  // "bar"(显式绑定)

箭头函数没有自己的 this,它继承外层执行上下文的 this:

const obj = {
  name: 'foo',
  greet: () => {
    console.log(this.name)  // 使用外层 this;若在 ES 模块顶层定义,这里会因 this 为 undefined 抛错
  }
}

词法作用域为什么重要

变量能不能被访问,首先由定义位置决定,而不是调用位置决定。这也是闭包成立的前提。

function makeCounter() {
  let count = 0
  return function() {
    return ++count
  }
}

const counter = makeCounter()
counter()  // 1
counter()  // 2

返回的函数保留包含 count 绑定的词法环境,即使 makeCounter 已返回;不意味着原来的调用栈帧仍然驻留。

延伸阅读:ecmascript-closures

边界与常见误解

  • 执行上下文不是作用域,但它们紧密关联。上下文包含作用域链信息。
  • var 的提升是创建并初始化为 undefined;let / const 只是创建,不初始化,所以 TDZ 会报错。
  • ES 模块顶层 this 是 undefined;浏览器经典脚本顶层通常是 window,Node.js CommonJS 文件顶层通常是 module.exports,不能统一说成 globalThis。
  • 严格模式下,函数独立调用时 this 是 undefined,而非全局对象。
  • 每次函数调用都创建新上下文,递归也不例外。

这篇和函数笔记怎么分工

  • 看函数定义、参数、call / apply / bind: ECMAScript 函数
  • 看执行时环境、提升、调用栈: 当前这篇
创建于 2025/1/1 更新于 2026/10/6
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本