定义
ECMAScript规范中对词法环境的描述如下:
词法环境是一种规范类型,用于根据ECMAScript代码的词法嵌套结构来定义标识符与特定变量和函数的关联关系。一个词法环境由环境记录(Environment Record)和一个可能为空的外部词法环境的引用(outer Lexical Environment)组成。 词法环境与ECMAScript代码的特定语法结构相关联,例如函数、代码块、
**TryCatch**中的**catch**从句,每次执行这类代码时都会创建新的词法环境。(作用域的原理)
就是相应代码块内,标识符与值的映射关系。词法环境是JS作用域的实现机制,通常又被称为作用域。
组成
词法环境有两个组成部分:
- 环境记录(Environment Record):记录词法环境中标识符与值得绑定
- 声明式环境记录(Declarative Environment Record):用来记录直接将标识符与值绑定的语法元素
- 函数环境记录(Function Environment Record):用于函数作用域
- 模块环境记录(Module Environment Record):用于体现一个模块的外部作用域
- 对象式环境记录(Object Environment Record):主要用于with和global的词法环境
- 全局环境记录(Global Environment Record):用于全局的词法环境中
- 声明式环境记录(Declarative Environment Record):用来记录直接将标识符与值绑定的语法元素
- 外部词法环境的引用(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对象式环境记录组件
- 每个对象式环境记录都有一个
binding object,全局环境记录的binding object就是全局对象,在浏览器内,全局this与window指向全局对象 - 绑定了所有内置的全局属性、全局函数以及全局的
var声明

3.2声明式环境记录组件
- 绑定了全局代码的其他声明,如let、const、class等ES6语法
- 因为声明式环境记录组件不是基于简单的对象形式来实现绑定,所有这些声明不能通过全局对象访问
外部词法环境引用(outer)
用于形成多个词法环境在逻辑上的嵌套结构,以实现可以访问外部词法环境的能力,称作环境链(作用域链) 作用域链就是沿着outer往上一层的词法环境里找变量/方法
首先说明两点:
- 全局环境的外部词法环境引用为null
- 一个词法环境可以作为多个词法环境的外部环境。例如全局函数的外部词法环境引用都指向全局词法环境
外部词法环境引用将一个词法环境和其他外部词法环境连接起来,外部词法环境又拥有其自身的外部引用,以此类推,这就形成了一个链式结构,称其为环境链(即作用域链的机制),全局环境是这条链的顶端
环境链的存在是为了标识符的解析,就是查找变量。首先在当前环境中查找变量,找不到就去外部环境找,还找不到就去外部环境的外部环境找,一次类推,直到找到或者到环境联顶端时还未找到则抛出 **ReferenceError**
案例分析
以下是全局代码
var a = 2;let x = 1;const y = 5;function foo() {console.log(a);function bar() {var b = 3;console.log(a * b);}bar();}function baz() {var a = 10;foo();}baz();
词法环境关系如图
以下是抽象的伪代码
// 全局词法环境
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
}
}
