ECMAScript执行上下文

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

#type / concept #status / growing #resource / javascript #resource / ecmascript

[!info] related notes

ECMAScript执行上下文

一句话定义

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

三个容易混淆的词

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

常见执行上下文类型

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

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

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

创建阶段

  • 确定外层词法环境引用
  • 处理参数
  • 建立声明绑定
  • 准备 thisnew.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]          // inner 执行完弹出
5. [GlobalContext]                        // outer 执行完弹出

每层上下文只能访问自己和外层的变量,不能访问同级或内层的。

提升与暂时性死区

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

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
}

TDZ 从块开始到 let / const 声明之间的区域,任何访问都会抛错。

this 绑定

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

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

obj.greet()          // "foo"(隐式绑定)
const fn = obj.greet
fn()                 // undefined(默认绑定,严格模式下也是 undefined)

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

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

const obj = {
  name: 'foo',
  greet: () => {
    console.log(this.name)  // 继承外层 this(通常是全局或外层函数)
  }
}

词法作用域为什么重要

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

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

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

返回的函数记住了 makeCounter 执行上下文中的 count,即使 makeCounter 已经执行完毕。

延伸阅读:ecmascript-closures

边界与常见误解

  • 执行上下文不是作用域,但它们紧密关联。上下文包含作用域链信息。
  • var 的提升是创建并初始化为 undefinedlet / const 只是创建,不初始化,所以 TDZ 会报错。
  • 全局上下文的 this 在浏览器中是 window,在 Node.js 中是 global(或 globalThis)。
  • 严格模式下,函数独立调用时 thisundefined,而非全局对象。
  • 每次函数调用都创建新上下文,递归也不例外。

这篇和函数笔记怎么分工

  • 看函数定义、参数、call / apply / bind: ecmascript-functions
  • 看执行时环境、提升、调用栈: 当前这篇
创建于 2025/1/1 更新于 2026/5/27