-
Notifications
You must be signed in to change notification settings - Fork 0
Description
1. 前言
之前在团队中做个一次 ppt 版的技术分享,这里用文字再整理一下,由于水平有限不保证内容的准确性。
2. 执行上下文
2.1 介绍
环境记录与执行上下文有一点的联系,所以在介绍环境记录之前先看了解一下什么是执行上下文。执行上下文表示用来跟踪当前程序的执行状态。执行上下文之间使用栈这样的结构来维护上下文与上下文之间的关系,栈顶的上下文称为当前运行执行上下文(running execution context)。执行上下文有三种类型:
- 全局执行上下文:程序启动后在代码执行前 js 引擎会初始化一系列程序执行所需要的环境,全局执行上下文就是其中之一。
- eval执行上下文:在调用内置函数 eval 对象时会创建一个对应的上下文。
- 函数执行上下文:函数被调用时产生的对应上下文,同一个函数每次执行的时候都会产生一个新的执行上下文
2.2 包含内容
对上下文有个大概的了解后我们来看看上下文中包含哪些部分,以下内容整理自 ecma262 规范文档。
| Component | Purpose |
|---|---|
| code evaluation state | 程序执行状态 |
| Function | 如果是函数执行上下文,表示该函数代码 |
| Realm | 提供内置对象,如: Function、Object、String等 |
| ScriptOrModule | 如果是 Script Recort 或 Module Record,则表示该代码 |
| LexicalEnvironment | 用于解析 let、const 所声明的标识符 |
| VariableEnvironment | 用于解析 var 语句 或 函数声明 |
| PrivateEnvironment | Identifies the PrivateEnvironment Record that holds Private Names created by ClassElements in the nearest containing class. null if there is no containing class. |
| Generator | 如果是生成器执行上下文,则表示该生成器代码 |
由于规范中对 PrivateEnvironment 的描述不是很理解,所以就把规范中的内容原样复制过来了,这部分推荐大家还是看规范文档好理解些,这里整理的内容是我所消化理解过的,不一定是正确的或者你不好理解的。
3. 环境记录
3.1 介绍
介绍完了执行上下文后,大家有没有看到执行上下文中的 LexicalEnvironment 和 VariableEnvironment。实际它们都是环境记录项,那现在我们来看一下什么环境记录。
以上是我根据规范内容所整理的一个类图,可以看到环境记录逻辑上可以看成一个面向对象中的一种单继承结构。其中 Environment Records 作为 Object Environment Records、declarative Environment Records、Global Environment Records 的基类,子类根据自己的算法去实现抽象类中的抽象方法。
3.2 基类的抽象方法
这里简单介绍一下每个方法的作用,具体的话需要看每个子类实现的算法,由于子类比较多这里就不整理了,文档中的算法步骤描述的也很清楚。
| 方法名 | 作用 |
|---|---|
| HasBinding(N) | 查看当前执行上下文中的环境记录项中是否存在标识符 N |
| CreateMutableBinding(N, D) | 创建一个标识符 N,D 表示可以在后续的操作中可以对该标识符进行删除 |
| CreateImmutableBinding(N, S) | 创建一个不可变的标识符 N,S 表示该上下文是否处于严格模式下,该标识符未初始化时进行访问会抛出一个异常 |
| InitializeBinding(N, V) | 初始化标识符 N 的值为 V |
| SetMutableBinding(N, V, S) | 设置环境记录中标识符 N 的值为 V,S 表示是否为严格模式,如果该标识符的值不能进行设置值时会抛出一个类型错误异常 |
| GetBindingValue(N, S) | 获取环境变量中标识符 N 的值,S 是否为严格模式,如果表示符为创建或者创建了未绑定则抛出引用错误异常 |
| DeleteBinding(N) | 删除环境记录中标识符 N |
| HasThisBinding() | 查看环境记录中是否存在 this 绑定 |
| HasSuperBinding() | 查看环境记录中是否存在 super 方法绑定 |
| WithBaseObject() | 如果当前环境记录为 Object Environment Records 时,表示绑定的对象 |
以上就是基类中的所有抽象方法,每个方法的具体算法步骤根据子类来定,比如 DeleteBinding(N) 方法只有当前环境记录为 Object Environment Records 时才有具体实现,别的子类直接返回 true。因为标识符本身是不能被删除的,只有对象的 key 才能被删除,Object Environment Records 表示我们使用 with 这样的语句才会创建对应的记录项,因为 with 中的标识符会与 with 的对象的 key 做一个关联绑定,当然 key 要符合规范文档中的标识符词法产生式(规则)。所以在 with 语句中我们可以对绑定的对象的 key 使用 delete 运算来达到 with 中的标识符删除。
4. 标识符解析
4.1 上下文中的环境记录结构
标识符的查找是从当前运行的执行上下文的环境记录中解析的。因为像 let 或者 const 变量声明加上块(block)会产生新的词法作用域。环境记录中 LexicalEnvironment 也会用栈来维护这样的结构,如:
function foo(){
var a = 1
let b = 2
{
let b = 3
var c = 4
let d = 5
console.log(a)
console.log(b)
}
console.log(b)
console.log(c)
console.log(d)
}
foo()以上代码大家应该都能看得出输出的顺序是什么吧。那用图表示就是一下这种结构
可以看到 LexicalEnvironment 中存在两个一样的标识符 b,程序在解析该标识符的时候先从 LexicalEnvironment 中的栈顶去解析。所以标识符 b 的内容是 3 而不是 2。
4.2 标识符解析完整过程
上面例子中的标识符都是存在当前执行上下文中的,那如果当前上下文中环境记录不存在要访问的标识符怎么解析呢?
实际每个环境记录都有一个 OuterEnv 引用,该引用指向词法结构上或代码结构上的外层环境记录,最外层环境记录的 OuterEnv 引用指向 null,所以标识符的查找过程很像对象中属性的查找过程,只不过标识符查找到最外层还没找到的时候就会报引用错误。属性的查找如果找不到结果会 undefined。
5. 最后
实际我在理解这些内容之后让我了解到为什么基础数据类型为什么存在栈中,而引用类型数据存在堆中。上下文是用栈这种结构来进行维护,表示符又是存在于上下文中,栈这种结构的特点是具有连续性,物理内存中申请一块连续大的内存空间资源是比较宝贵的,哪怕内存空间足够大因为空间的不连续也容易导致内存申请失败。所以栈中的内存释放是在该上下文被校会的时候就会被释放掉。堆中的内存需要垃圾回收机制算法来释放对应的内存空间,所以堆的一个特点我理解的就是可以存放生命周期比较长的数据如闭包,可以存放比较占用空间资源的数据,比如对象,栈中只不过存储对象的引用。如果理解有误或者有什么错误的地方欢迎大家一起来讨论。

