定义

ECMAScript规范中对词法环境的描述如下:

词法环境是一种规范类型,用于根据ECMAScript代码的词法嵌套结构来定义标识符与特定变量和函数的关联关系。一个词法环境由环境记录(Environment Record)和一个可能为空的外部词法环境的引用(outer Lexical Environment)组成。 词法环境与ECMAScript代码的特定语法结构相关联,例如函数、代码块、**TryCatch** 中的 **catch** 从句,每次执行这类代码时都会创建新的词法环境。(作用域的原理)

就是相应代码块内,标识符与值的映射关系。词法环境是JS作用域的实现机制,通常又被称为作用域。

组成

词法环境有两个组成部分:

  1. 环境记录(Environment Record):记录词法环境中标识符与值得绑定
    • 声明式环境记录(Declarative Environment Record):用来记录直接将标识符与值绑定的语法元素
      • 函数环境记录(Function Environment Record):用于函数作用域
      • 模块环境记录(Module Environment Record):用于体现一个模块的外部作用域
    • 对象式环境记录(Object Environment Record):主要用于with和global的词法环境
    • 全局环境记录(Global Environment Record):用于全局的词法环境中
  2. 外部词法环境的引用(outer):用于形成多个词法环境在逻辑上的嵌套结构,以实现可以访问外部词法环境的能力

不同环境下,构成不同

  • 全局环境:没有外部引用的词法环境,外部环境引用为null
  • 模块环境:包含模块顶层声明绑定的词法环境,外部环境引用是全局环境或者外部函数的词法环境
  • 函数环境:包含模块顶级声明的绑定,模块显式导入的绑定,外部环境就是全局环境

环境记录(Environment Record)

记录词法环境中所有标识符与值得绑定,可以抽象为一个对象

环境记录分为三种类型,分别是声明式环境记录(Declarative Environment Record)对象式环境记录(Object Environment Record)全局环境记录(Global Environment Record)

1. 声明式环境记录(Declarative Environment Record)

记录直接将标识符与值绑定的语法元素,如let、const、class、module、import以及函数声明

声明式环境记录有两种类型函数环境记录(Function Environment Record)模块环境记录(Module Environment Record)

1.1函数环境记录(Function Environment Record)

函数中的环境记录,用于体现一个函数的作用域;如果函数不是箭头函数,还会提供一个 this 的绑定,绑定到调用者的环境

1.2模块环境记录(Module Environment Record)

模块中的环境记录,用于体现一个模块的外部作用域(即模块export所在环境),除了正常绑定外,也提供了所有引入的其他模块的绑定(即import的所有模块,这些绑定只读)因此我们可以直接访问引入的模块

2. 对象式环境记录(Object Environment Record)

仅用于with和全局的词法环境,记录将标识符与值绑定的语法元素,如with、全局var、全局函数等ES5语法 每个对象式环境记录都与一个对象相关联,这个对象被称为绑定对象(binding object

该环境记录只绑定一组字符串的标识符名称,并在绑定对象上映射同名属性与绑定值。

  • 对象式记录就是基于绑定对象的,它本身只记录拥有的标识符名称,并不存储绑定的值
  • 而是会在绑定对象上创建同名属性,并赋予对应的绑定值,即将映射关系存储在绑定对象里了
  • 由于对象是可变的,因此该记录中的标识符都是可变绑定,对象式环境记录中不存在不可变的绑定

这是为了ES5的问题,函数声明和var全局变量,都会被添加到window对象上,并可以通过window对象直接访问。

3. 全局环境记录(Global Environment Record)

全局词法环境中的环境记录,在逻辑上来说是单个记录,但是它被指定为封装对象式环境记录声明式环境记录的组合,即本身不是一个单独的记录类型,而是对一个对象式环境记录和一个声明式环境记录的封装

3.1对象式环境记录组件

  1. 每个对象式环境记录都有一个 binding object ,全局环境记录的 binding object就是全局对象,在浏览器内,全局this与window指向全局对象
  2. 绑定了所有内置的全局属性、全局函数以及全局的 var 声明

词法环境(Lexical Environment) - 图1

3.2声明式环境记录组件

  1. 绑定了全局代码的其他声明,如let、const、class等ES6语法
  2. 因为声明式环境记录组件不是基于简单的对象形式来实现绑定,所有这些声明不能通过全局对象访问

词法环境(Lexical Environment) - 图2

外部词法环境引用(outer)

用于形成多个词法环境在逻辑上的嵌套结构,以实现可以访问外部词法环境的能力,称作环境链(作用域链) 作用域链就是沿着outer往上一层的词法环境里找变量/方法

首先说明两点:

  1. 全局环境的外部词法环境引用为null
  2. 一个词法环境可以作为多个词法环境的外部环境。例如全局函数的外部词法环境引用都指向全局词法环境

外部词法环境引用将一个词法环境和其他外部词法环境连接起来,外部词法环境又拥有其自身的外部引用,以此类推,这就形成了一个链式结构,称其为环境链(即作用域链的机制),全局环境是这条链的顶端

环境链的存在是为了标识符的解析,就是查找变量。首先在当前环境中查找变量,找不到就去外部环境找,还找不到就去外部环境的外部环境找,一次类推,直到找到或者到环境联顶端时还未找到则抛出 **ReferenceError**

案例分析

以下是全局代码

  1. var a = 2;
  2. let x = 1;
  3. const y = 5;
  4. function foo() {
  5. console.log(a);
  6. function bar() {
  7. var b = 3;
  8. console.log(a * b);
  9. }
  10. bar();
  11. }
  12. function baz() {
  13. var a = 10;
  14. foo();
  15. }
  16. baz();

词法环境关系如图词法环境(Lexical Environment) - 图3

以下是抽象的伪代码

// 全局词法环境
GlobalEnvironment = {
    outer: null, //全局环境的外部环境引用为null
    GlobalEnvironmentRecord: {
        //全局this绑定指向全局对象
        [[GlobalThisValue]]: ObjectEnvironmentRecord[[BindingObject]],
        //声明式环境记录,除了全局函数、全局var、内置属性,其他声明都绑定在这里
        DeclarativeEnvironmentRecord: {
            x: 1,
            y: 5
        },
        //对象式环境记录,绑定对象为全局对象
        ObjectEnvironmentRecord: {
            a: 2,
            foo:<< function>>,
            baz:<< function>>,
            isNaNl:<< function>>,
            isFinite: << function>>,
            parseInt: << function>>,
            parseFloat: << function>>,
            Array: << construct function>>,
            Object: << construct function>>
            ...
            ...
        }
    }
}

// foo函数词法环境
fooFunctionEnviroment = {
      // 外部词法环境引用指向全局环境
    outer: GlobalEnvironment,
      // 函数词法记录
    FunctionEnvironmentRecord: {
        [[ThisValue]]: GlobalEnvironment,//this绑定指向全局环境
        bar:<< function>> 
    }
}

// bar函数词法环境
barFunctionEnviroment = {
      //外部词法环境引用指向foo函数词法环境
    outer: fooFunctionEnviroment,
      // 函数词法记录
    FunctionEnvironmentRecord: {
        [[ThisValue]]: GlobalEnvironment,//this绑定指向全局环境
        b: 3
    }
}

//baz函数词法环境
bazFunctionEnviroment = {
      //外部词法环境引用指向全局环境
    outer: GlobalEnvironment,
    // 函数词法记录
    FunctionEnvironmentRecord: {
        [[ThisValue]]: GlobalEnvironment,//this绑定指向全局环境
        a: 10
    }
}