[系列]从ECMAScript Specification中学习执行上下文之Environment Records(上)

967 阅读5分钟

这个系列的文章主要是对ES标准的第9章进行介绍,通过标准的角度来理解我们经常会谈起的执行上下文及相关的一些概念。这一章的内容非常多,因此将会分为多篇文章,本篇首先介绍Environment Records。本文使用当前最新的标准文档(2021-05-06),链接如下:tc39.es/ecma262/

什么是Environment Record

从字面上不难看出,它是Record结构(wiki),可以参照ts的Record类型,可用于实现映射关系,标准中的定义为:

Environment Record 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

总结一下定义中的几个要点:

  • Environment Record只是标准中的一种抽象类型,具体的实现中是无法获取到的
  • 它的作用是基于代码的词法嵌套结构来定义标识符和具体变量或函数之间的关联

简而言之,当我们的代码中出现了一个标识符,那么这个标识符肯定就代表了某个变量或者函数,Environment Record就是用于记录这个关联关系的。

一个Environment Record的出现通常是和特定的语法结构相关联的,例如:模块函数声明代码块catch语句with语句等,一旦这一类代码被解析时,就会创建一个Environment Record来记录这些代码所生成的标识符的绑定。

后面的内容可能会使用Env简写来表示Environment Record

从Environment Record到Environment Records

每一个Environment Record都有一个[[OuterEnv]]字段,其值可能是null也可能是一个指向外部Env的引用,通过这个字段可以表示Env之间在逻辑上的嵌套关系。

[[OuterEnv]]指向是一个外部 Env,Outer Env在逻辑上包围了内部Env,同时Outer Env也会有[[OuterEnv]]字段,以此类推,就形成了一条链,这在后面的标识符解析时会用到。比如我们在全局环境下有以下代码:

function outer() {
    function inner1() {

    }

    function inner2() {
        
    }
}

这个情况下,我们可以粗略地将Env之间的嵌套关系表示为下图(只是粗略的表示,因为上下文和Env之间并不是1对1的关系):

image.png

Environment Record的类型

上文中曾经提到过,Env的创建会和不同的语法结构有关系,而不同的语法所创建的Env也是存在一些差异的。标准中用了面向对象的形式来表示不同的Env以及它们之间的关系:其中Environment Record是一个抽象类,它有3个具体的子类:declarative Environment Record, object Environment Record, global Environment Record。此外,Function Environment Recordmodule Environment Record又是declarative Environment Record的子类。我们可以用一个UML来简单表示一下:

image.png

declarative Environment Record

定义脚本代码中直接将标识符和ECMAScript语言类型的值相绑定的效果,例如函数声明、var变量声明、catch字句。

function Environment Record

和脚本中的函数对象调用相关,包含了函数内部的顶层绑定;除此之外,它可能会形成一个新的this绑定,并且支持必要的super方法的调用。

module Environment Record

包含了模块内部顶层的绑定,注意这里的模块指的是es6 module,同时它还包含了模块内import语句所引入的显式绑定。它的[[OuterEnv]]指向的global Env

object Environment Record

定义脚本代码中将标识符和对象的属性相绑定的效果,例如with语句,这里大家可以联想一下with语句的使用效果。但是由于with语句在严格模式下是被禁用的,后续的内容也不会对object Environment Record进行详细的介绍了。

global Environment Record

用于脚本的全局声明,它的[[OuterEnv]]为null;它会被预填充一些绑定并且包含了一个全局对象,全局对象的属性会提供部分标识符绑定。比如在浏览器环境下,我们在全局代码中直接写console.log(name),此时并不会报错,原因就是name是全局对象window的一个属性,而这个属性就被预填充到了global Env中,因此可以直接访问到。当然,想location,history等对象也是类似的情况。

Environment Record的抽象方法

上文中提到过,Environment Record可以视为一个抽象类,它包含了一些抽象方法,这些抽象方法是每一种具体子类都必须要实现的。这些方法均是围绕标识符绑定相关的操作,具体包括:

方法含义
HasBinding(N)返回Env是否存在对字符串值N的绑定
CreateMutableBinding(N, D)创建一个uninitialized的可变绑定,字符串N代表绑定的名称;D是一个布尔值,代表该绑定是否可以删除
CreateImmutableBinding(N, S)创建一个uninitialized的不可变绑定,字符串N代表绑定的名称;S是一个布尔值,如果为true,如果在该绑定被初始化之后再进行设置,那么直接抛错
InitializeBinding(N, V)为一个已被创建但是uninitialized的绑定设置一个value,字符串N代表绑定的名称;V代表绑定值,可以是任何一种ECMAScript语言类型
SetMutableBinding(N, V, S)为已存在的可变绑定设置value, 字符串N代表绑定的名称;V代表绑定值,可以是任何一种ECMAScript语言类型; S是一个布尔值,如果为true,那么该绑定无法被设置时将会抛出错误
GetBindingValue(N, S)获取绑定名称N的值,S是一个布尔值,如果为true,那么当绑定不存在或者未初始化时将直接报错
DeleteBinding(N)删除绑定名称N,如果绑定存在并且能够删除时,则删除绑定并返回true,否则返回false;绑定不存在时直接返回false
HasThisBinding()判断Env是否创建了this绑定
HasSuperBinding()判断Env是否创建了super绑定
WithBaseObject()如果Env是和with语句相关联,则返回with语句的对象;否则返回undefined

不同类型的具体Environment Record在实现这些方法时的逻辑时会存在一些差异,并且可能会添加其他的属性或方法,我们将在下篇文章中具体介绍这些实现逻辑。