溫故而知新 - 從新認識JavaScript的Execution Context

更新下相關知識,立足過往,擁抱將來。javascript

概念

直接看規範關於ExecutionContext的定義:java

An execution context is a specification device that is used to track the runtime evaluation of code by an ECMAScript implementation.

ExecutionContext爲抽象概念,用來描述可執行代碼的執行環境。可執行代碼的運行,都是在ExecutionContext中。git

管理方式ExecutionContextStack

Execution context Stack爲後進先出(LIFO)的棧結構。棧頂永遠是running execution context。當控制從當前execution context對應可執行代碼轉移到另外一段可執行代碼時,相應的execution context將被建立,並壓入棧頂,執行結束,對應的execution context從棧頂彈出。github

思索:什麼ECMAScript特性會使Execution context stack不遵循LIFO規則?

規範裏面提到:ide

Transition of the running execution context status among execution contexts usually occurs in stack-like last-in/first-out manner. However, some ECMAScript features require non-LIFO transitions of the running execution context.

而後在規範裏面,並無找到some ECMAScript features究竟是什麼特性。不過,第一反應,Generator算不算?在stackoverflow上,有這麼一個討論Execution Context Stack. Violation of the LIFO order using Generator Function函數

function *gen() {
  yield 1;
  return 2;
}

let g = gen();

console.log(g.next().value);
console.log(g.next().value);

調用一個函數時,當前execution context暫停執行,被調用函數的execution context建立並壓入棧頂,當函數返回時,函數execution context被銷燬,暫停的execution context得以恢復執行。post

如今,使用的是GeneratorGenerator函數的execution context在返回yield表達式的值以後仍然存在,並未銷燬,只是暫停並移交出了控制權,可能在某些時候恢復執行。學習

到底是不是呢?有待求證。ui

詞法環境Lexical Environments

看規範的定義:this

A Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code.

按規範來講,Lexical Environment定義了標識identifiersVariablesFunctions的映射。

組成 Lexical Environment包含兩部分:

  • Environment Record
    記錄被建立的標識identifiersVariablesFunctions的映射
類型 簡述
Declarative Environment Records 記錄 varconstletclassimportfunction等聲明
Object Environment Records 與某對象綁定,記錄該對象中string identifier的屬性,非string identifier的屬性不會被記錄。Object environment recordswith語句所建立
Function Environment Records Declarative Environment Records的一種,用於函數的頂層,若是爲非箭頭函數的狀況,提供this的綁定,若還引用了 super則提供super方法的綁定
Global Environment Records 包含全部頂層聲明及global object的屬性,Declarative Environment RecordsObject Environment Records的組合
Module Environment Records Declarative Environment Records的一種,用於ES module的頂層,除去常量和變量的聲明,還包含不可變的import的綁定,該綁定提供了到另外一environment records的間接訪問
  • 外部Lexical Environment的引用
    經過引用構成了嵌套結構,引用可能爲null

分類 Lexical Environment分三類:

  • Global Environment
    沒有外部Lexical EnvironmentLexical Environment
  • Module Environment
    包含了模塊頂層的聲明以及導入的聲明,外部Lexical EnvironmentGlobal Environment
  • Function Environment
    對應於JavaScript中的函數,其會創建this的綁定以及必要的super方法的綁定

變量環境Variable Environments

在ES6前,聲明變量都是經過var聲明的,在ES6後有了letconst進行聲明變量,爲了兼容var,便用Variable Environments來存儲var聲明的變量。

Variable Environments實質上仍爲Lexical Environments

機制

具體能夠參考規範ECMAScript 2019 Language Specification。相關的是在8.3 Execution Contexts

一篇很不錯的文章參考Understanding Execution Context and Execution Stack in Javascript, 該文章的中文翻譯版中文版

參考裏面的例子:

var a = 20;
var b = 40;
let c = 60;

function foo(d, e) {
    var f = 80;
    
    return d + e + f;
}

c = foo(a, b);

建立的Execution Context像這樣:

GlobalExecutionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      c: < uninitialized >,
      foo: < func >
    }
    outer: <null>,
    ThisBinding: <Global Object>
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
      a: undefined,
      b: undefined,
    }
    outer: <null>, 
    ThisBinding: <Global Object>
  }
}

在運行階段,變量賦值已經完成。所以GlobalExecutionContext在執行階段看起來就像是這樣的:

GlobalExecutionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      c: 60,
      foo: < func >,
    }
    outer: <null>,
    ThisBinding: <Global Object>
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
      a: 20,
      b: 40,
    }
    outer: <null>, 
    ThisBinding: <Global Object>
  }
}

當遇到函數foo(a, b)的調用時,新的FunctionExecutionContext被建立並執行函數中的代碼。在建立階段像這樣:

FunctionExecutionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      Arguments: {0: 20, 1: 40, length: 2},
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>,
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      f: undefined
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>,
  }
}

執行完後,看起來像這樣:

FunctionExecutionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      Arguments: {0: 20, 1: 40, length: 2},
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>,
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      f: 80
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>,
  }
}

在函數執行完成之後,返回值會被存儲在c裏。所以GlobalExecutionContext更新。在這以後,代碼執行完成,程序運行終止。

總結

ECMAScript規範是年年都在更新,得與時俱進的增強學習,立足過往及當下,擁抱將來!

參考資料

  1. 全部的函式都是閉包:談 JS 中的做用域與 Closure
  2. JS夯實之執行上下文與詞法環境
  3. 結合 JavaScript 規範來談談 Execution Contexts 與 Lexical Environments
  4. You-Dont-Know-JS 2nd-ed
  5. ECMAScript 2019 Language Specification
  6. Understanding Execution Context and Execution Stack in Javascript
相關文章
相關標籤/搜索