ES 基础 —— 执行上下文

published

前言

Execution Context(执行上下文)是 ECMA-262 标准中定义的一个抽象概念,用于同 Executable Code(可执行代码)进行区分。

标准中并没有从技术实现的角度定义 Execution Context 准确类型和结构,以各引擎的具体实现为准。

Executable Code

合法的,可以被解释器解析执行的代码。

Excutable Code 有这么几类:

  • Global Code
  • Function Code:函数体内的代码
  • Eval Code:使用 eval() 函数执行的代码

Execution Context

Execution Context 是 ES 用来跟踪代码运行状态和相关资源集合的特殊机制。它决定了 Executable Code 执行的过程中可以访问的数据。

依据 Excutable Code 的分类, Excution Context 也有相应的分类:

  • Global Execution Context
  • Function Execution Context
  • Eval Execution Context

Execution Context 的基本工作方式

解释基本工作方式的过程中,会涉及到两个名词,先解释一下:

  • Execution Context Stack:用来保存所有 Execution Context 的栈。
  • Running Execution Context:正在使用的 Execution Context。在任意时间,最多只能有一个正在运行代码的 Execution Context。

基本工作方式:Runnig Execution Context 总是在 Execution Context Stack 的顶部,Global Execution Context 总在 Execution Context Stack 的底部。无论什么时候,只要控制权从与当前 Running Execution Context 相关的可执行代码上切换到另一部分与当前 Running Execution Context 不相关的可执行代码上,一个新的 Execution Context 就会被创建,新创建的 Execution Context 会被放在当前的 Runnig Execution Context 的上面,成为新的 Running Execution Context。

      ╔═══════════════════════════════╗
      ║  ┌─────────────────────────┐  ║
      ║  │Running Execution Context│  ║
      ║  └─────────────────────────┘  ║
      ║  ┌─────────────────────────┐  ║
      ║  │    Execution Context    │  ║
      ║  └─────────────────────────┘  ║
      ║                               ║
      ║              ■    Execution   ║
      ║              ■  Context Stack ║
      ║              ■                ║
      ║                               ║
      ║  ┌─────────────────────────┐  ║
      ║  │    Execution Context    │  ║
      ║  └─────────────────────────┘  ║
      ║  ┌─────────────────────────┐  ║
      ║  │    Execution Context    │  ║
      ║  └─────────────────────────┘  ║
      ║  ┌─────────────────────────┐  ║
      ║  │Global Execution Context │  ║
      ║  └─────────────────────────┘  ║
      ╚═══════════════════════════════╝

Execution Context 的具体工作流程

如前言中提到的,ES 标准中并没有从技术实现的角度定义 Execution Context 准确类型和结构,为了更方便地解释 Excutable Code 和 Execution Context 之间的关系,暂且用数组表示 Execution Context Stack,然后用伪代码来操作 Execution Context Stack(省得画图了):

ECStack = []

解释器在解析 Executable Code 时,为其创建对应的 Execution Context。

Global Code 与 Global Execution Context

ECStack = [
  globalContext
]

Function Code 与 Function Execution Context

每一次函数调用都会创建新的 Function Execution Context。比如:

(function foo(bar) {
  if (bar) {
    return
  }

  foo(true);
})()


// 第一次调用 foo
ECStack = [
  <foo> functionContext,
  globalContext
]

// 第二次调用 foo
ECStack = [
  <foo> functionContext – recursively,
  <foo> functionContext,
  globalContext
]

Eval Code 与 Eval Execution Context

Eval Code 引入了一个新的概念 —— Calling Context(调用上下文),这是一个当 eval() 函数被调用时才会产生的 Context,并且 eval() 的活动(变量声明或函数声明)会影响到 Calling Context 。

// influence global context
eval('var x = 10')

(function foo() {
  // and here, variable 'y' is created
  // in the locale context of 'foo' function
  eval('var y = 20')
})()

console.log(x)  // 10
console.log(y)  // 'y' is not defined

在严格模式中, eval() 将在本地沙盒(local sandbox)中执行,不再影响 Calling Context

上面的例子执行过程中,ECStack 的变化如下:

ECStack = [
  globalContext
]

// eval('var x = 10')
ECStack.push({
  context: evalContext,
  callingContext: globalContext,
})

// eval exited context
ECStack.pop()

// foo function call
ECStack.push(<foo> functionContext)

// eval('var y = 20')
ECStack.push({
  context: evalContext,
  callingContext: <foo> functionContext
})

// return from eval
ECStack.pop()

// return from foo
ECStack.pop()

Execution Context 的创建过程

  1. 解释器发现 Executable Code。
  2. 创建 Execution Context 并执行 Executable Code:
    • Creation Stage (创建阶段):
      • 创建 Scope Chain(当前 Execution Context 的 VO 和所有词法范畴上的父级 Execution Context 的 VO)。
      • 创建 Variable Object(简称 VO,又名 Activation Object):
        1. 创建 arguments 对象。
        2. 扫描函数声明并创建对应属性:
          • 对于扫描到的函数声明,在 Variable Object 中创建一个属性:属性名和函数名相同,属性值是指向内存中函数位置的指针。
          • 如果属性名已经存在,指针将被覆写。
        3. 扫描变量声明并创建对应属性:
          • 对于扫描到的变量声明,在 Variable Object 中创建一个属性:属性名同变量名相同,属性值初始化为 undefined
          • 如果属性名已经存在,什么都不做。
      • 创建 this 属性。
    • Activation / Code Execution Stage (执行阶段):在上一步创建的 Execution Context 解释执行 Excutable Code。

无法通过代码来直接访问 Variable Object,只有解析器才能访问它。

上面,关于 Scope Chain 的中文描述不怎么准确,来看这段英文:The scope chain property of each Execution Context is simply a collection of the current Execution Context’s Variable Object + all parent’s lexical Variable Object.

举一个例子来解释这个过程:

function foo(i) {
  var a = 'hello'
  var b = function privateB() {}

  function c() {
  }
}

foo(22)

调用 foo(22) 的时候,会在 Creation Stage 中创建这样的 Variable Object:

fooExecutionContext = {
  scopeChain: [ ... ],
  variableObject: {
    arguments: {
      0: 22,
      length: 1,
    },
    i: 22,
    c: pointer to function c(),
    a: undefined,
    b: undefined,
  },
  this: { ... }
}

能看到,在 Creation Stage 中,虽然定义了属性名,但是并没有对它们初始化。Creation Stage 完成后,开始 Code Execution Stage:

fooExecutionContext = {
  scopeChain: [ ... ],
  variableObject: {
    arguments: {
      0: 22,
      length: 1,
    },
    i: 22,
    c: pointer to function c()
    a: 'hello',
    b: pointer to function privateB()
  },
  this: { ... }
}

参考