结合 JavaScript 规范来谈谈 Execution Contexts 与 Lexical Environments
引言
重新回头看看 JavaScript 的内容,做一做查漏补缺,却相关的资料繁多:有些谈论着什么 Variable Object、Activation Object,有些又在谈论着 LexicalEnvironment 与 VariableEnvironment。所讲述的内容虽然大抵类似,但是一番东西却变着出来两番叫法,总是叫人怀疑。想来还是该寻根溯源,到 ECMAScript 规范来找寻答案。
作用域
JavaScript 中的作用域是 词法作用域,与动态作用域相对,其定义在词法阶段,函数的作用域基于函数创建的位置。
词法作用域意味着作用域时由书写代码时函数声明的位置来决定的。编译的词法分析阶段基本能够知道全部标识符在哪里以及是如何声明的,从而能够预测在执行过程中如何对它们进行查找。
– YDKjs
Execution Context & Execution Context Stack
Execution context(执行上下文)是什么?先来看看规范中给出的定义:
An execution context is a specification device that is used to track the runtime evaluation of code by an ECMAScript implementation.
– ECMAScript® 2019 Language Specification
简单来理解,JavaScript 引擎会为可执行的代码创建对应的 execution context,这一“可执行的代码”可能是全局代码或是函数代码等。
Execution context stack 用于追踪、存储这些 execution contexts。一如其名,execution context stack 是一个后进先出(LIFO)的栈结构。在栈顶上永远是 running execution context,当控制从当前 context 对应的代码转移到另一段代码时,相应的 execution context 将被创建,新的 execution context 会被压入栈顶。同时,栈底总会有一个 global execution context。
下图是一个简单的 execution context stack 示例:
图片来自 Understanding Execution Context and Execution Stack in Javascript
值得注意的是,虽然 execution context stack 是栈结构,但这并不意味着 JS 引擎只能严格按照 LIFO 的方式处理 context,有些 ECMAScript 的特性会导致其违反 LIFO 的规则:
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.
– ECMAScript® 2019 Language Specification
Execution context 中包含多个状态:code evaluation state、Function、Realm、ScriptOrModule 等,但最值得我们关注的是其中的 LexicalEnvironment 以及 VariableEnvironment。这两者均为 Lexical Environment ,只不过针对的声明种类不同。
Lexical Environments
首先来看看 Lexical Environment 在规范中的定义:
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.
– ECMAScript® 2019 Language Spec