词法环境(Lexical Environments)
官方规范对词法环境的说明是:词法环境(Lexical Environments)是一种规范类型,用于根据ECMAScript代码的词法嵌套结构来定义标识符与特定变量和函数的关联。词法环境由一个环境记录(Environment Record)和一个可能为空的外部词法环境(outer Lexical Environment)引用组成。通常,词法环境与ECMAScript代码的特定语法结构相关联,例如FunctionDeclaration,BlockStatement或TryStatement的Catch子句,并且每次执行这样的代码时都会创建新的词法环境
环境记录记录了在其关联的词法环境作用域内创建的标识符绑定。它被称为词法环境的环境记录。环境记录也是一种规范类型。规范类型对应于在算法中用来描述ECMAScript语言结构和ECMAScript语言类型的语义的元值。
全局环境是一个没有外部环境的词法环境。全局环境的外部环境引用为null。
模块环境是一个包含模块顶层声明绑定的词法环境。模块环境的外部环境是一个全局环境。
函数环境是一个对应于ECMAScript函数对象调用的词法环境。
环境记录(Environment Record)
ES8规范中主要使用两种环境记录值:声明性环境记录和对象环境记录。环境记录是一个抽象类,它具有三个具体的子类,分别是声明式环境记录,对象环境记录和全局环境记录。其中全局环境记录在逻辑上是单个记录,但是它被指定为封装对象环境记录和声明性环境记录的组合。
对象环境记录(Object Environment Record)
每个对象环境记录都与一个对象联系在一起,这个对象被称为绑定对象(binding object)。一个对象环境记录绑定一组字符串标识符名称,直接对应于其绑定对象的属性名称。无论绑定对象自己的和继承的属性的[[Enumerable]]设置如何,它们都包含在集合中。由于可以动态地从对象中添加和删除属性,因此对象环境记录绑定的一组标识符可能会因为任何添加或删除对象属性操作的副作用而改变。即使相应属性的Writable的值为false。因此由于这种副作用而创建的任何绑定都将被视为可变绑定。对象环境记录不存在不可变的绑定。
var withObject={
a:1,
foo:function(){
console.log(this.a);
}
}
with(withObject){
a=a+1;
foo(); //2
}
- 创建新的词法环境。
- 接着创建了一个对象环境记录即为OER,OER包含withObject这个绑定对象,OER中的字符串标识符名称列表为withObject中的属性«a,foo»,在with语句中的变量操作默认在绑定对象中的属性中优先查找。
- 为OER设置外部词法环境引用。
- ps:注意:对象环境记录不是指Object里面的环境记录。普通的Object内部不存在新的环境记录,它的环境记录就是定义该对象所在的环境记录。
声明性环境记录(Declarative Environment Record)
每个声明性环境记录都与包含变量,常量,let,class,module,import和/或function的声明的ECMAScript程序作用域相关联。声明性环境记录绑定了包含在其作用域内声明定义的标识符集。这句话很好理解,举个例子如下:
import x from '***';
var a=1;
let b=1;
const c=1;
function foo(){};
class Bar{};
//这时声明性环境记录中就有了«x,a,b,c,foo,Bar»这样一组标识符,当然实际存放的结构肯定不是这个样子的,还要复杂。
函数环境记录(Function Environment Record)
函数环境记录是一个声明性环境记录,它用来表示function中的顶级作用域,此外如果函数不是一个箭头函数(ArrowFunction),则为这个函数提供一个this绑定。如果一个函数不是一个ArrowFunction函数并引用了super,则它的函数环境记录还包含从该函数内执行super方法调用的状态。
函数环境记录有下列附加的字段
| 字段名称 | 值 | 含义 |
|---|---|---|
| [[ThisValue]] | Any | 用于该函数调用的this值 |
| [[ThisBindingStatus]] | "lexical" ,"initialized" ,"uninitialized" | 如果值是“lexical”,这是一个ArrowFunction,并且没有一个本地的this值。 |
| [[FunctionObject]] | Object | 一个函数对象,它的调用导致创建该环境记录 |
| [[HomeObject]] | Object或者undefined | 如果关联的函数具有super属性访问权限,并且不是一个ArrowFunction,则[[HomeObject]]是该函数作为方法绑定的对象。 [[HomeObject]]的默认值是undefined。 |
| [[NewTarget]] | Object或者undefined | 如果该环境记录是由[[Construct]]的内部方法创建的,则[[NewTarget]]就是[[Construct]]的newTarget参数的值。否则,它的值是undefined。 |
我简单介绍一下这些字段,[[ThisValue]]这个字段的值就是函数中的this对象,[[ThisBindingStatus]]中"initialized" ,"uninitialized"看字面意思也知道了,主要是“lexical”这个状态为什么是代表ArrowFunction,我的理解是ArrowFunction中是没有一个本地的this值,所以ArrowFunction中的this引用不是指向调用该函数的对象,而是根据词法环境进行查找,本地没有就向外部词法环境中查找this值,不断向外查找,直到查到this值,所以[[ThisBindingStatus]]的值是“lexical”。看下面例子:
var a = 'global.a';
var obj1 = {
a:'obj1.a',
foo: function(){
console.log(this.a);
}
}
var obj2 = {
a:'obj2.a',
arrow:()=>{
console.log(this.a);
}
}
obj1.foo() //obj1.a
obj2.arrow() //global.a不是obj2.a
obj1.foo.bind(obj2)() //obj2.a
obj2.arrow.bind(obj1)() //global.a 强制绑定对ArrowFunction没有作用
对ArrowFunction中this的有趣的说法就是:我没有this,你送我个this我也不要,我就喜欢拿别人的this用,this还是别人的好。
[[FunctionObject]]:在上一个例子中指得就是obj1.foo、obj1.arrow。
[[HomeObject]]:只有函数有super访问权限且不是ArrowFunction才有值。看个MDN上的例子:
var obj1 = {
method1() {
console.log("method 1");
}
}
var obj2 = {
method2() {
super.method1();
}
}
Object.setPrototypeOf(obj2, obj1);
obj2.method2(); //method 1
//在这里obj2就是[[HomeObject]]
//注意不能这么写:
var obj2 = {
foo:function method2() {
super.method1(); //error,function定义下不能出现super关键字,否则报错。
}
}
[[NewTarget]]:构造函数才有[[Construct]]这个内部方法,如用new关键词调用的函数就会有[[Construct]],newTarget参数我们可以通过new.target在函数中看到。
function newTarget(){ console.log(new.target); } newTarget() //undefined new newTarget() /*function newTarget(){ console.log(new.target); } new.target指代函数本身*/