JS 执行过程:执行上下文、词法环境
执行上下文(Execution Contexts)
执行上下文(Execution Contexts):是一种规范设备,用于跟踪ECMAScript实现对代码的运行时评估 —— JavaScript 代码都需要运行在相应的“执行上下文”中
执行上下文一共有三种类型:
- 全局执行上下文:基础的上下文,任何不在函数内部的代码都在其中。
- 它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。
- 程序首次允许时创建 —— 一个程序中只会有一个全局执行上下文。
- 函数执行上下文:函数内部的代码所在的上下文。
- 每次函数被调用都会创建一个新的执行上下文 —— 同一函数的多次执行,会多个上下文
- eval执行上下文:eval() 内部的代码所在的执行上下文
- 使用“eval方法”时创建
在任何时间点,最多有一个“执行上下文”在实际执行代码,这被称为“正在运行的执行上下文”(running execution context)。
执行上下文栈
执行上下文栈(Execution context stack,ECS):又称“调用栈 / 执行栈”(call stack),用于跟踪“执行上下文” —— “正在运行的执行上下文”就是这个其的栈顶元素
函数的“执行过程”就是:对应的“执行上下文”(栈帧)在“调用栈”中的“入栈”(开始执行)、“出栈”(执行完毕)操作。
示例:
let a = 'Hello World!'; function first() { console.log('Inside first function'); second(); console.log('Again inside first function'); } function second() { console.log('Inside second function'); } first(); console.log('Inside Global Execution Context');
状态组件(State Components)
组件(Component) | 用途(Purpose) |
---|---|
“所有执行上下文”的状态组件 | |
代码评估状态(code evaluation state) | 对“与执行上下文关联的代码的执行、挂起和恢复”进行评估所需的任何状态。 |
函数(Function) | 如果这个执行上下文正在评估函数对象的代码,那么这个组件的值就是那个函数对象。如果上下文正在评估脚本或模块的代码,则该值为null。
|
领域(Realm) | “关联代码”访问“ECMAScript 资源”的领域
|
“ECMAScript代码执行上下文”的附加状态组件 | |
词法环境(LexicalEnvironment) | 标识“词法环境”,用于解析代码在此执行上下文中造成的标识符引用。 |
变量环境(VariableEnvironment) | 标识“词法环境”,其 EnvironmentRecord 保存由 VariableStatements 在此执行上下文中创建的绑定。 |
“生成器执行上下文”的附加状态组件 | |
词法环境(LexicalEnvironment) | 标识“词法环境”,用于解析代码在此执行上下文中造成的标识符引用。 |
执行上下文的 LexicalEnvironment 和 VariableEnvironment 组件始终是“词法环境(Lexical Environments)”。 创建执行上下文时,其 LexicalEnvironment 和 VariableEnvironment 组件最初具有相同的值。
生命周期
可以分为 2 个主要阶段:
- 创建阶段(Creation Phase):(“预编译阶段”)JavaScript 引擎会做以下几件事情:
- 创建“词法环境(Lexical Environment)”组件
- 创建“变量环境(Variable Environment)”组件
- 执行阶段(Code Execution Phase):(“执行阶段”)JavaScript 引擎会按照代码的顺序逐行执行,并且根据需要更新变量和函数的值
- 在执行阶段,如果 JavaScript 引擎不能在源码中声明的实际位置找到 let 变量的值,它会被赋值为
undefined
。
- 在执行阶段,如果 JavaScript 引擎不能在源码中声明的实际位置找到 let 变量的值,它会被赋值为
词法环境(Lexical Environment)
词法环境(Lexical Environment):是一种规范类型,基于 ECMAScript 代码的“词法嵌套结构”来定义“标识符”和具体“变量 / 函数”的关联 —— 标识符:变量 / 函数的名字 —— 标识符:对“原始数据”或“实际对象”(包含函数类型对象)的引用 ⭐ 即:词法环境,是一种持有“标识符-变量”映射的结构
通常,词汇环境与 ECMAScript 代码的某些特定语法结构相关联,如 FunctionDeclaration、BlockStatement、Try 语句的 Catch 子句,每次评估此类代码时都会创建一个新的词汇环境。
特殊的“词法环境”:
- 全局环境(global environment):是一个没有外部环境的词汇环境(“外部词法环境引用”为 null)。
- 全局环境的 EnvironmentRecord 可以预先填充标识符绑定,并包含关联的“全局对象”,该对象的属性提供了一些全局环境的标识符绑定;
- 这个“全局对象”是全局环境的 This 绑定的值;
- 在执行 ECMAScript 代码时,可以向“全局对象”添加其他属性,并修改初始属性;
- 模块环境(module environment):是一个包含模块的顶级声明的绑定的词法环境。
- 它还包含由模块显式导入的绑定;
- 其“外部词法环境引用”是一个“全局环境”;
- 函数环境(function environment):是一个“对应于ECMAScript函数对象的调用”的词汇环境。
- 它可以建立一个新的 this 绑定;
- 它还将捕获支持 super 方法调用所需的状态;
⭐ 词法环境,三个部分组成:
- 一个“环境记录(Environment Record)”
- 一个“(可能为空值的)外部词法环境引用(Outer Lexical Environment)”
- 以及“This 绑定(This Binding)”
其伪代码如下:
// 全局执行上下文 GlobalExectionContext = { // 词法环境 LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // 环境记录: "对象式环境记录" // 在这里绑定标识符 }, outer: <null>, // 外部词法环境引用: null this: <global object> // this 绑定: "全局对象" } } // 函数执行上下文 FunctionExectionContext = { // 词法环境 LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // 环境记录: "声明式环境记录" // 在这里绑定标识符 }, outer: <Global or outer function environment reference>, // 外部词法环境引用: 外部"词法环境" this: <depends on how function is called> // this 绑定: 取决于函数调用方式 } }
环境记录(Environment Record)
环境记录(Environment Record):记录在其关联的“词法环境”的范围内创建的“标识符”绑定 —— 任何在“环境记录”中的标识符都可以在当前“词法环境”直接以标识符形式访问
两种主要的“环境记录”:
- 声明式环境记录(Declarative Environment Record):记录 范围内的声明
- 主要用于“函数”和“catch”词法环境
- 对象式环境记录(Object Environment Record):记录 绑定对象的属性
- 主要用于“with”和 global 的词法环境
- 全局环境记录(Global Environment Record):一个对“对象式环境记录”和“声明式环境记录”组件的封装
声明式环境记录(Declarative Environment Record)[ECMAScript 1]
声明式环境记录:与ECMAScript 程序范围相关联,将绑定其范围内包含的声明(declarations)所定义的标识符集 💡“ECMAScript 程序范围”包括:variable、constant、let、class、module、import、function declarations 等所定义的标识符集
注意:
- 其方法(CreateMutableBinding、CreateImmutableBinding):
- 要求该 binding 在创建前必须不存在 —— 【let 无法重定义?】
- 会将该 binding 标记为 uninitialized ——【let 的暂时性死区?】
- 其方法(GetBindingValue):如果该 binding 是一个 uninitialized binding,将引发 ReferenceError
对象式环境记录(Object Environment Record)[ECMAScript 2]
对象式环境记录:与一个绑定对象(binding object)相关联,将绑定与其“绑定对象”的属性名称直接对应的字符串标识符名称集 💡“绑定对象(binding object)”包括:全局对象(即浏览器环境的“Window 对象”)、With 语句关联的对象
注意:
- 其方法(CreateMutableBinding):
- 并不要求该 binding 在创建前必须不存在 ——【var 可重定义?】
- 会将该 binding 初始化为 undefined ——【var 提升?】
- “标识符”将与“binding object”的属性一一对应:
- 示例:
// 定义的标识符 var x = 1000 // 可以通过如下方式获取 console.log(window.x)
// binding object 中的属性 window.y = 999 // 也可以通过标识符直接获取 console.log(y)
- 无论
[[Enumerable]]
属性的设置如何,集合中始终包含了“自身的”和“继承的”属性 - 因为对象可以动态添加和删除属性,所以绑定的“标识符”集一定是可变的
全局环境记录(Global Environment Record)[ECMAScript 3]
全局环境记录:在逻辑上是单个记录,但被指定为封装“对象环境记录(Declarative Environment Record)”和“声明性环境记录(Object Environment Record)”的组合 —— 专门用于脚本全局声明
其中:
- “对象环境记录”:将关联领域(Realm)的全局对象作为其基础对象,包括:
- 所有 built-in globals(内置全局变量)的绑定,
- 全局代码中 FunctionDeclaration、GeneratorDeclaration 或 VariableStatement 引入的所有绑定
- “声明性环境记录”:全局代码中所有其他 ECMAScript 声明的绑定
外部词法环境引用(Outer Lexical Environment)
外部词法环境引用(Outer Lexical Environment):用于对词法环境值的“逻辑嵌套”进行建模 —— 意味着通过它可以访问其“外部词法环境”
如果在当前的“词法环境”中找不到变量,JavaScript 引擎可以在外部环境中查找这些变量:
- (内部)词汇环境的外部引用是指在逻辑上围绕内部词汇环境的词汇环境
- 外部词汇环境可能有自己的外部词汇环境
- 一个词汇环境可以作为多个内部词汇环境的外部环境
每个“词法环境”都使用“外部词法环境引用”与其“外部词法环境”链接,如此将形成一个“环境链”(ES 3 中的作用域链) —— 用于“标识符的解析”(变量的查找)
This 绑定(This Binding)
⭐ This Binding:
- 在“全局执行上下文”中:this 的值指向“全局对象”
- 浏览器中,“全局对象”即
Window
对象
- 浏览器中,“全局对象”即
- 在“函数执行上下文”中:this 的值取决于函数调用方式:
- 若函数被一个引用对象调用,this 将指向对象,
- 否则,this 的值被设置为“全局对象”(非严格模式)或者
undefined
(严格模式)
补充内容
“执行上下文”、“词汇环境”和“环境记录”纯粹是规范机制,不需要与 ECMAScript 实现的任何特定内容相对应。 ECMAScript 程序不可能直接访问或操作这些值。
LexicalEnvironment 与 VariableEnvironment
LexicalEnvironment 和 VariableEnvironment:都是执行上下文的组件,且同是“词法环境(Lexical Environments)” 创建执行上下文时,其 LexicalEnvironment 和 VariableEnvironment 组件最初具有相同的值。 —— 【注意:“Lexical Environments”指是“词法环境”这一规范机制,而“LexicalEnvironment”才指执行上下文的“词法环境”组件】
- “词法环境(LexicalEnvironment)”:用于记录范围内的“函数声明”和“变量声明”(let 和 const)的绑定
- 有“全局作用域”、“函数作用域”及“块作用域”
- “变量环境(VariableEnvironment)”:仅记录“var 变量”的绑定
- 只有“全局作用域”和“函数作用域”
⭐ “变量环境(VariableEnvironment)”的概念是为 ES6 服务的:为了实现 var 的变量提升 “变量环境(VariableEnvironment)”:其 EnvironmentRecord 保存由 VariableStatements(var 变量)在此“执行上下文”中创建的绑定
完整总结
- 全局执行上下文:
- “词法环境”:
- “对象式环境记录”:记录全局的“函数、变量(let, const)”等声明
- “外部词法环境引用”:为 null
- “This 绑定”:为“全局对象”(浏览器环境,即 Window 对象)
- “变量环境”:
- “对象式环境记录”:记录全局的“变量 var”的声明
- ...
// 全局执行上下文 GlobalExectionContext = { // 词法环境 LexicalEnvironment: { EnvironmentRecord: { // 环境记录: "对象式环境记录" Type: "Object", /** * 在此处绑定标识符 * 包括: "函数声明"和"变量声明"(let, const)的绑定 * */ } // 外部词法环境引用: null outer: <null>, // this 绑定: "全局对象" ThisBinding: <Global Object> }, // 变量环境 VariableEnvironment: { EnvironmentRecord: { // 环境记录: "对象式环境记录" Type: "Object", /** * 在此处绑定标识符 * 仅: "变量声明"(var)的绑定 * */ } // 外部词法环境引用: null outer: <null> , // this 绑定: "全局对象" ThisBinding: <Global Object> } }
- “词法环境”:
- 函数执行上下文:
- “词法环境”:
- “声明式环境记录”:包含:
- 函数的“函数、变量(let, const)”等声明
- 传递给函数的“arguments 对象” —— (对象存储索引与参数的映射、参数的 length)
- “外部词法环境引用”:为“外部词法环境 / 全局词法环境”
- “This 绑定”:取决于函数调用方式(及是否严格模式)
- “声明式环境记录”:包含:
- “变量环境”:
- “声明式环境记录”:记录函数的“变量 var”的声明
- ...
// 函数执行上下文 FunctionExectionContext = { // 词法环境 LexicalEnvironment: { EnvironmentRecord: { // 环境记录: "声明式环境记录" Type: "Declarative", /** * 在此处绑定标识符 * 包括: "函数声明"和"变量声明"(let, const)的绑定 * 以及: 传递给函数的 arguments 对象 * */ }, // 外部词法环境引用: "外部词法环境"或"全局词法环境" outer: <Global or outer function environment reference>, // this 绑定: 取决于函数调用方式 ThisBinding: <depends on how function is called>, }, // 变量环境 VariableEnvironment: { EnvironmentRecord: { // 环境记录: "声明式环境记录" Type: "Declarative", /** * 在此处绑定标识符 * 仅: "变量声明"(var)的绑定 * */ }, // 外部词法环境引用: "外部词法环境"或"全局词法环境" outer: <Global or outer function environment reference>, // this 绑定: 取决于函数调用方式 ThisBinding: <depends on how function is called>, } }
- “词法环境”:
案例分析
以如下代码为例:
let a = 20; const b = 30; var c; function multiply(e, f) { var g = 20; return e * f * g; } c = multiply(20, 30);
分析 JavaScript 引擎执行过程:
- “全局执行上下文”的“创建阶段”:
- 创建“词法环境(Lexical Environment)”、“变量环境(Variable Environment)”组件
GlobalExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here a: <uninitialized> , b: <uninitialized> , multiply: <func> } outer: <null> , ThisBinding: <Global Object> }, VariableEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here c: undefined, } outer: <null> , ThisBinding: <Global Object> } }
- “全局执行上下文”的“执行阶段”:
- 按照代码顺序逐行执行,并按需更新变量和函数的值
GlobalExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here a: 20, b: 30, multiply: <func> } outer: <null>, ThisBinding: <Global Object> }, VariableEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here c: undefined, } outer: <null>, ThisBinding: <Global Object> } }
- “函数执行上下文”的“创建阶段”:
- 创建“词法环境(Lexical Environment)”、“变量环境(Variable Environment)”组件
FunctionExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here Arguments: {0: 20, 1: 30, length: 2}, }, outer: <GlobalLexicalEnvironment>, ThisBinding: <Global Object or undefined>, }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here g: undefined }, outer: <GlobalLexicalEnvironment>, ThisBinding: <Global Object or undefined> } }
- ThisBinding:“非严格模式”为“Global Object”,“严格模式”为“undefined”
- “函数执行上下文”的“执行阶段”:
- 按照代码顺序逐行执行,并按需更新变量和函数的值
FunctionExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here Arguments: {0: 20, 1: 30, length: 2}, }, outer: <GlobalLexicalEnvironment>, ThisBinding: <Global Object or undefined>, }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here g: 20 }, outer: <GlobalLexicalEnvironment>, ThisBinding: <Global Object or undefined> } }
- “全局执行上下文”的“执行阶段”:
- 按照代码顺序逐行执行,并按需更新变量和函数的值
GlobalExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here a: 20, b: 30, multiply: <func> } outer: <null>, ThisBinding: <Global Object> }, VariableEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here c: 12000, } outer: <null>, ThisBinding: <Global Object> } }
参考
ECMAScript
以下内容来自:《ECMAScript® 2015 Language Specification》的 《Executable Code and Execution Contexts》
- ↑
声明式环境记录(Declarative Environment Record),定义/描述:
声明性环境记录用于定义 ECMAScript 语言语法元素的效果,如 FunctionDeclarations、VariableDeclarations 和 Catch 子句,这些子句将“标识符绑定”与“ECMAScript 语言值”直接关联。
- “ECMAScript 语言值(ECMAScript language values)”:由“ECMAScript语言类型(ECMAScript Language Types)”表征的值
- “ECMAScript语言类型(ECMAScript Language Types)”:包括 Undefined、Null、Boolean、String、Symbol、Number 和 Object
每个声明性环境记录都与 ECMAScript 程序范围相关联,该程序范围包含 variable(变量), constant(常量), let, class, module, import, and/or function declarations。声明性环境记录绑定其范围内包含的声明所定义的标识符集。
- ↑
对象式环境记录(Object Environment Record),定义/描述:
对象环境记录用于定义 ECMAScript 元素(如 WithStatement)的效果,这些元素将“标识符绑定”与“某些对象的属性”相关联。
每个对象“环境记录”都与一个称为其“绑定对象(binding object)”的对象相关联。对象环境记录将绑定与其“绑定对象”的属性名称直接对应的字符串标识符名称集。不是 IdentifierName 形式的字符串的属性键不包括在绑定标识符集中。无论 [[Enumerable]] 属性的设置如何,集合中都包含了自己的和继承的属性。因为可以从对象中动态添加和删除属性,所以由对象 Environment Record 绑定的标识符集可能会发生更改,这可能是任何添加或删除财产的操作的副作用。由于这种副作用而创建的任何绑定都被认为是可变绑定,即使相应属性的 Writable 属性的值为 false。对象“环境记录”不存在不可变绑定。
- ↑
全局环境记录(Global Environment Record),定义/描述:
全局环境记录和函数环境记录是专门用于脚本全局声明和函数中的顶级声明的专门化。
全局环境记录用于表示在 common Realm(公共领域)中处理的所有 ECMAScript 脚本元素共享的最外部作用域。全局环境记录为 built-in globals(内置全局变量)、properties of the global object(全局对象的属性)以及脚本中出现的所有顶级声明提供绑定。
全局环境记录在逻辑上是单个记录,但它被指定为封装“对象环境记录(Declarative Environment Record)”和“声明性环境记录(Object Environment Record)”的组合。“对象环境记录”将关联 Realm(领域)的全局对象作为其基础对象。这个全局对象是全局环境记录的 GetThisBinding 具体方法返回的值。全局环境记录的对象环境记录组件包含所有 built-in globals(内置全局变量)的绑定,以及全局代码中包含的 FunctionDeclaration、GeneratorDeclaration 或 VariableStatement 引入的所有绑定。全局代码中所有其他 ECMAScript 声明的绑定都包含在全局环境记录的“声明性环境记录”组件中。
可以直接在全局对象上创建属性。因此,全局环境记录的对象环境记录组件可能既包含由 FunctionDeclaration、GeneratorDeclassion 或 VariableDeclassing 声明显式创建的绑定,也包含作为全局对象的属性隐式创建的链接。为了识别哪些绑定是使用声明显式创建的,全局环境记录会维护一个使用其 CreateGlobalVarBindings 和 CreateGlobalFunctionBindings 具体方法绑定的名称列表。