Lexical environments: ECMAScript implementation

原文

ECMA-262-5 in detail. Chapter 3.2. Lexical environments: ECMAScript implementation.

简介

在之前的3.1章。我们讨论了词法环境的整体理论。我们还特别讨论了与之相关的静态作用域(static scope)和闭包(closures)。我们还提到ECMAScript所采用的链式环境帧模型(the model of chained environment frames)。在这一章,我们将用ECMAScript去实现词法环境(lexical environments)。我们要关注实现过程中的结构和术语是如何体现这个普遍的理论。我们先从定义开始。尽管之前我们已经给出了词法环境在普遍理论中的定义,在这里我们给出在ECMA-262-5中标准的定义。

定义

在我们之前提到的理论中,环境是用来管理嵌套的代码块中的数据,例如变量,函数等等。在ECMAScript中也是如此。
在ECMAScript的代码中,词法环境依据词法上嵌套的结构来确定标识符与变量值和函数的关联。我们也提到过这种名称与值的关联叫做绑定。在ECMAScript中,词法环境由两部分构成:一条环境记录(environment)和一个指向外部环境的引用。环境的定义与我们之前讨论的模型中的(frame)相对应。因此,一条环境记录记录这个词法环境中创建的标识符的绑定。换句话说,一条环境记录保存了出现在上下文中的变量。
考虑下面这个例子

var x = 10;
function foo() {
  var y = 20;
}

于是我们有了两个抽象的环境,分别对应中全局上下文和foo函数的上下文:

// environment of the global context

globalEnvironment = {
  environmentRecord: {
    // built-ins:
    Object: function,
    Array: function,
    // etc ...
    
    // our bindings:
    x: 10
  },

  outer: null // no parent environment
}

// environment of the "foo" function

fooEnvironment of the "foo" function

fooEnvironment = {
  environmentRecord: {
    y: 20
  },
  outer: globalEnvironment
}

outer引用是用来链接当前环境和父环境的。父环境当然也有自己的outer链接。全局环境的外部链接被设为null。全局环境是作用域链(chain of scopes)的终点。这让人想起原型继承是如何在ECMAScript中工作的。如果在对象本身上没有发现属性,就会去查找该对象的原型,若没有就是原型的原型,直到原型连的终点。环境和这个一样,上下文中出现的变量或标识符代表属性,外部链接代表指向原型的引用。一个词法环境可能包裹多个内部的词法环境。例如,一个函数内部有两个函数,那么内部的函数的词法环境的外部环境就是包裹它们的函数。

function foo() {
  var x = 10;
  function bar() {
    var y = 20;
    console.log(x + y); // 30
  }

  function baz() {
    var z = 30;
    console.log(x + z); // 40
  }
}

// ----- Environments -----

// "foo" environment

fooEnvironment = {
  environmentRecord: {x: 10},
  outer: globalEnvironment
};

// both "bar" and "baz" have the same outer
// environment -- the environment of "foo"

barEnvironment = {
  environmentRecord: {y: 20},
  outer: fooEnvironment
};

bazEnvironment = {
  environmentRecord: {z: 30},
  outer: fooEnvironment
}

环境记录类型

ECMAScript定义了两种环境记录类型:声明式环境记录(declarative environment records)和对象环境记录(object environment records)

声明式环境记录

声明式环境记录是用来处理函数作用域中出现的变量,函数,形参等。例如

// all: "a", "b" and "c"
// bindings are bindings of 
// a declarative record

function foo(a) {
  var b = 10;
  function c() {}
}

在大多数场合中,声明记录保存绑定被认为是在底层实现的。这是和ES3中活动对象概念的主要的不同。换句话说,不要求声明记录被当作一个普通对象的方式来实现,那样很低效。这意味着声明式环境记录并被直接暴露给用户,我们无权访问这些绑定,即记录的属性。实际上,我们以前也不可以,即使在ES3中,我们也无法直接访问活动对象。潜在的,声明式记录允许采用词法地址技术(lexical addressing technique),这能够直接去访问需要的变量,而不用去作用域链上查找,无论作用域嵌套的有多深。ES5的标准文档里并没有直接提到这个事实。我们要用声明式环境记录替换旧的活动对象的概念,它们的实现效率就不一样。Brendan Eich也提到

the activation object implementation in ES3 was just “a bug”: “I will note that there are some real improvements in ES5, in particular to Chapter 10 which now uses declarative binding environments. ES1-3’s abuse of objects for scopes (again I’m to blame for doing so in JS in 1995, economizing on objects needed to implement the language in a big hurry) was a bug, not a feature”.

一条声明式环境记录可以这样表现

environment = {
  // storage
  environmentRecord: {
    type: "declarative",
    // storage
  },
  // reference to the parent environment
  outer: <...>
};

对象环境记录

相比之下,对象环境记录是用来确定全局环境和with声明中出现的变量和函数的。它们被当作普通对象来实现,效率低。在这样的上下文中,用来存储绑定的对象叫绑定对象(binding object)。在全局环境下,变量被绑定来全局对象上。

var a = 10;
console.log(a); // 10

// "this" in the global context
// is the global object itself
console.log(this.a); // 10

一条对象环境记录可以这样表现

environment = {
  // storage
  environmentRecord: {
    type: "object",
    bindingObject: {
      // storage
    }
  },
  // reference to the parent environment
  outer: <...>
};

执行环境的结构

在这里,我们简单介绍下ES5中执行上下文的结构。与ES3有些不同,它有以下属性:

ExecutionContextES5 = {
  ThisBinding: <this value>,
  VariableEnvironment: { ... },
  LexicalEnvironment: { ... },
}

this绑定

在全局环境中,this仍然是全局对象本身

(function (global) {
  global.a = 10;
})(this);
 
console.log(a); // 10

在环境对象中,this仍然取决于函数是怎样被调用的。如果被引用调用(called with a reference), 那么这个引用的所有者(the base value of the reference)就是这个this

var foo = {
  bar: function () {
    console.log(this);
  }
};
 
// --- Reference cases ---
 
// with a reference
foo.bar(); // "this" is "foo" - the base
 
var bar = foo.bar;
 
// with the reference
bar(); // "this" is the global, implicit base
this.bar(); // the same, explicit base, the global
 
// with also but another reference
bar.prototype.constructor(); // "this" is "bar.prototype"

变量环境

变量环境就是存储上下文中的变量和函数的。当我们进入一个函数环境中,arguments对象就被创建,保存来形参的值。

function foo(a) {
  var b = 20;
}
 
foo(10);

它的变量环境

fooContext.VariableEnvironment = {
  environmentRecord: {
    arguments: {0: 10, length: 1, callee: foo},
    a: 10,
    b: 20
  },
  outer: globalEnvironment
};

词法环境

[译者注:额,变量环境的拷贝,与with有关,不译了]
闭包保存来创造它的上下文的词法环境。

标识符解析

标识符解析是依据词法环境决定上下文中标识符的绑定。换句话说,它就是作用域的查找。与上文中提到的原型链类似。

var a = 10;
 
(function foo() {
 
  var b = 20;
 
  (function bar() {
 
    var c = 30;
    console.log(a + b + c); // 60
     
  })();
 
})();

解析a的过程如下

function resolveIdentifier(lexicalEnvironment, identifier) {
  
  // if it's the final link, and we didn't find
  // anything, we have a case of a reference error
  if (lexicalEnvironment == null) {
    throw ReferenceError(identifier + " is not defined");
  }
  
  // return the binding (reference) if it exists;
  // later we'll be able to get the value from the reference
  if (lexicalEnvironment.hasBinding(identifier)) {
    return new Reference(lexicalEnvironment, identifier);
  }
  
  // else try to find in the parent scope,
  // recursively analyzing the outer environment
  return resolveIdentifier(lexicalEnvironment.outer, identifier);
 
}
 
resolveIdentifier(bar.[[LexicalEnvironment]], "a") ->
 
-- bar.[[LexicalEnvironment]] - not found,
-- bar.[[LexicalEnvironment]].outer (i.e. foo.[[LexicalEnvironment]]) -> not found
-- bar.[[LexicalEnvironment]].outer.outer -> found reference, value 10
    原文作者:nbb3210
    原文地址: https://segmentfault.com/a/1190000011909388
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞