Environment Records
Environment Record 是一种用于定义标识符(Identify)与特定变量和函数之间关联的规范类型,基于ECMAScript代码的词法嵌套结构。通常, 环境记录与代码的某些特定语法相关联,例如函数声明FunctionDeclaration,块语句BlockStatement或者捕获子语句TryStatement。 每次在执行此类代码之前(解释时),都会创建一个新的环境记录来记录该代码创建的标识符绑定。
每个环境记录都有一个[[OuterEnv]]
字段(为空或者对外部环境记录的引用)。这用于模拟环境记录值的逻辑嵌套。一个(内部)环境记录的外部引用是对逻辑
包围内部环境记录的引用。外部环境记录也有自己的外部环境记录,环境记录可以作为多个内部环境记录的外部环境。
举个例子,一个FunctionDeclaration包含了两个嵌套的FunctionDeclaration
,那么每个嵌套函数的外部环境记录将是周围函数当前评估的环境记录。
Pitfall
注意,环境记录纯粹是规范机制,不需要对其实现,程序也不能直接访问和操作其值
环境记录的层次结构
环境记录可以被认为是存在于一个简单的面向对象的层次结构中,其中环境记录是一个抽象类,有三个具体的子类:
- 声明式环境记录:用于定义ECMAScript语言语法元素的效果,如函数声明,变量声明和Catch子句,它们将标识符绑定与ECMAScript语言值相关联。
- 函数环境记录:对应于函数对象的调用,并包含该函数中顶层声明的绑定,它还可以建立一个新的
this
绑定,还可以捕获支持super
方法调用的必要状态。 - 模块环境记录:包含模块顶层声明的绑定,它还包含模块显示导入的绑定,它的
[[OuterEnv]]
是一个全局环境记录。
- 函数环境记录:对应于函数对象的调用,并包含该函数中顶层声明的绑定,它还可以建立一个新的
- 对象环境记录:用于定义ECMAScript元素的效果,如将标识符绑定与某个对象的属性相关联的WithStatement。
- 全局环境记录:用于脚本全局声明,它没有外部环境,所以
[[OuterEnv]]
为空,它可以预先填充标识符绑定,并且包括相关联的全局对象,其属性提供全局环境的一些 标识符绑定。当代码被执行时,可以向全局对象添加额外的属性,并且可以修改初始属性。
环境记录抽象类包含了这些规范方法,这些抽象方法对每一个子类都有不同的具体算法。
声明式环境记录
每个声明式环境记录都与包含var
、const
、let
、class
、module
、import
和function声明的程序范围相关联。
声明性环境记录的具体规范方法的行为由以下算法定义。
对象环境记录
每个对象环境记录关联了一个绑定对象,对象环境记录将一组字符串标识符名称绑定到其绑定对象的属性名称。
不是以标识符名称形式的字符串属性键不包括在绑定标识符的集合中,无论其[[Environment]]
属性如何设置,自身的和继承的属性都包含在集合中,
因为属性可以动态的添加到或者从对象中删除,所以对象环境记录绑定的标识符集合可能会因为添加或者删除属性的操作而产生潜在的变化。
由这种副作用产生的任何绑定都被视为可变绑定,即使相应属性的Writable属性为false。对象环境记录不存在不可变绑定。
with语句和对象环境记录
with语句主要用来扩展作用域链,使得在指定对象的属性可以直接访问,而无需每次都使用对象名来访问属性。
with(obj) {//}
Pitfall
with 语句已弃用,可能存在混淆错误和兼容性问题。而且存在性能问题(需要更长的作用域链上查找)
函数环境记录
Note
函数环境记录是声明性环境记录,用于表示函数的顶级作用域,如果函数不是ArrowFunction,则提供this绑定。 如果函数箭头函数且引用了super,则其函数环境记录还包括用于从函数内部执行super方法调用的状态。
函数环境记录是JavaScript中重要的一环,主要用于存储函数执行时的变量和参数信息,每当一个函数被调用时,JavaScript引擎就会创建 一个新的环境记录来保存该函数的。
函数环境记录核心概念:
- 作用域链:每个环境记录都有一个指向外部环境记录的引用(
[[OuterEnv]]
字段),这形成了作用域链,通过这个链,函数可以访问其外部的 作用域中的变量。 - 变量存储:环境记录会存储函数内部定义的变量,参数以及其他数据,这些信息在函数执行期间是可用的。
- 执行上下文:环境记录是执行上下文的一部分。执行上下文还包括其他信息,如this的值和代码的执行状态。
- 创建销毁:当函数被调用时,新的环境记录被创建,函数执行完毕后,该环境记录会被销毁,释放内存。
- 闭包:环境记录在闭包中起着重要作用,闭包允许函数访问其外部环境记录中的变量,即使外部函数已经执行完毕。
全局环境记录
全局环境记录表示所有在公共领域中处理的ECMAScript脚本元素共享的最外层范围。全局环境记录提供了[内置全局变量的绑定],[全局对象的属性], 以及脚本中发生的所有顶级声明。
全局环境记录核心特性:
[[OuterEnv]]
字段为空,作用连的终点。- 内置全局变量的绑定,如
Math
,JSON
等 - 全局对象,浏览器中
window
,nodeJS中global
- 全局环境记录中,所有顶级声明都会被存储,这些声明在整个脚本的声明周期内是可用的。
- 当代码动态执行时,可以动态的修改全局对象的属性
模块环境记录
模块环境记录是一个声明性的环境记录,用于表示ECMAScript模块的外部范围。
它的主要特点包括:
- 作用域:模块环境记录为模块内的所有顶级声明提供了一个独立的作用域,确保模块内的变量和函数不会与其他模块或全局作用域中的标识符冲突。
- 导入和导出:模块环境记录支持模块的导入和导出机制,允许模块之间共享功能和数据。通过
import
和export
语句,模块可以显式地声明其依赖关系和提供的接口。 - 封装性:模块环境记录提供了更好的封装性,模块内部的实现细节可以被隐藏,外部只能通过导出的接口进行访问。这有助于减少全局命名冲突和提高代码的可维护性。
- 静态分析:由于模块的结构是静态的,JavaScript引擎可以在编译时进行优化,提升模块的加载和执行效率。
- 生命周期:模块环境记录的生命周期和模块的加载和执行周期相对应,模块加载时,环境记录被创建,模块执行完毕时,环境记录被销毁。
模块环境记录的设计使得ECMAScript模块能够以一种清晰和可管理的方式组织代码,促进了模块化编程的实践。
PrivateEnvironment records
PrivateEnvironment records是一种特殊的环境记录,主要被用来实现私有变量和封装,基于ClassDeclaration和ClassExpressions, 都关联着PrivateEnvironment records,当类被执行时,私有环境记录就会被创建且记录着这个类中声明的私有变量。
class Counter {#count; // 私有字段constructor() {this.#count = 0; // 初始化私有字段}increment() {this.#count++; // 访问私有字段}getCount() {return this.#count; // 访问私有字段}}
Realms
Note
ECMAScript262: 在所有ECMAScript代码执行之前,所有的代码都必须与一个Realm相关联。概念上讲,Reamls由一组固有对象,一个全局环境,在 该全局环境内加载的所有代码以及其他相关的状态和资源组成。
通俗来讲,Realms是一个独立的执行环境,包含了自己的全局对象(window
或者global
)、原生对象(Array、Function
等)和其他的相关的
状态。
Realm的作用主要提供一个隔离的执行环境,使得不同的代码可以在互不干扰的情况下运行。对于安全性、模块化和多线程编程场景非常重要。
既然Realm是执行的环境,那么就有以下几种类型:
- 浏览器环境:
- 全局对象
window
- 全局变量和函数
- 内置原生对象
- 环境记录
- DOM和BOM
- 全局对象
- NodeJS环境
- Module Realm
- Web Worker Realm
- Iframe
- Service Worker Realm
- Shared Worker Realm