ECMA-262-5 词法环境:ECMA实现(三)--- 执行上下文

来源:互联网 发布:java中国天气网api 编辑:程序博客网 时间:2024/05/21 09:13

执行上下文的结构

  这部分主要讨论在ES5中执行上文(同执行环境)的结构,它与ES3中的有一些不同,先看看它的组成组件:

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

  在执行上下文中同时包含词法环境组件和变量环境组件,其中执行上下文的词法环境和变量环境组件始终为 词法环境 对象,这一点可能很容易造成混淆。下面也将会介绍它们的区别,但在这先简单的说下它们的区别在于函数声明(FD)和函数表达式(FE)中[[scope]]值的不同。
  下面来分别看下执行上下文中的组成。

this绑定

  this值现在被叫做this绑定,虽然术语换了,但在语法角度上来说没有太大区别(除了在严格模式下,this值可以是undefined)。在全局上下文中,this绑定仍然是全局对象。

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

  在函数内部的执行上下文中,this值仍然是由函数被调用的方式所决定的。我们知道,存在一个引用类型(Reference type),而引用类型的base属性的值就是this的值,这在所有情况下都是用,无论是全局对象或者严格模式下值为undefined的this绑定。

var foo = {  bar: function () {    console.log(this);  };};// --- Reference cases ---// with a referencefoo.bar(); // "this" is "foo" - the basevar bar = foo.bar;// with the referencebar(); // "this" is the global, implicit basethis.bar(); // the same, explicit base, the global// with also but another referencebar.prototype.constructor(); // "this" is "bar.prototype"// --- non-Reference cases ---(foo.bar = foo.bar)(); // "this" is "global" or "undefined"(foo.bar || foo.bar)(); // "this" is "global" or "undefined"(function () { this; })(); // "this" is "global" or "undefined"

  注意,下面这种方式在严格模式下已无法获得全局对象:

(function () {  "use strict";  var global = (function () { return this; })();  console.log(global); // undefined!})();

  关于这部分的具体内容可以在严格模式(注二)一章中找到。
  下面来到我们要讨论的变量环境组件和词法环境组件,关于这两部分内容经常会造成混淆,而且在一些文本中的描述也并不正确。

变量环境

  变量环境是在进入执行上下文阶段时,用来储存变量声明和函数声明的初始值。这一点很像ES3中的变量对象VO/AO。
  当进入函数的执行上下文时,创建一个特殊的激活对象AO来替代VO,通过函数的arguments属性初始化,用来储存函数的形参等。它由三部分组成:

  • callee — 指向当前函数的引用;
  • length — 真正传递的参数的个数;
  • properties-indexes(字符串类型的整数) 属性的值就是函数的参数值(按参数列表从左到右排列)。 properties-indexes内部元素的个数等于arguments.length.

  properties-indexes 的值和实际传递进来的参数之间是共享的(注意这一点)。
  现在在严格模式下,激活对象已与过往不同。其实arguments属性不再与函数的实际形参之间共享,同时callee属性也被移除。
看下面代码:

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

  下面是它的变量环境结构:

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

  那什么是词法环境呢?有意思的一点是,其词法环境组件和变量环境组件最初是同一个值,词法环境复制于变量环境,变量环境组件永远不会变,而词法环境组件可能会变。

词法环境

  无论是变量环境还是词法环境都是属于词法环境的概念(先不用管这里名字上造成的困惑),都是在上下文中静态的(词法的)决定内部函数的外部绑定。
  我们上面提到过,在初始化阶段(当上下文被激活),词法环境就是变量环境的一个副本,就以上面这个例子来看:

fooContext.LexicalEnvironment = copy(fooContext.VariableEnvironment);

  它们的改变发生在代码执行阶段,与with语句和catch从句的词法环境扩张有关。
  在上面讲过,with语句和catch从句会在代码执行阶段替代掉当前的执行环境。下面先看下讨论函数表达式。
  我们知道,在函数创建阶段,会创建一个闭包来保存它创建时所在上下文的词法环境。如果一个函数表达式是创建在with语句(或catch从句)中,它同样应该保存当前的词法环境。
  按照规则,用with创建的环境替代了当前变量环境后,当with执行完成,又会把当前环境恢复过来。然而这也意味着,此时函数表达式已无法访问到在with执行时创建的闭包内容,但函数表达式的确需要这些内容。
  而且,实际上(with创建的环境)是不能替换掉当前的变量环境,因为函数声明同样可以在with语句中被调用,并且不同于函数表达式,函数声明应该继续使用它创建时绑定的初始状态值而不是来自with对象。
  对此,我们要知道。函数声明会保存变量环境作为它[[scope]]属性的值。对于函数表达式,在这个例子中,函数表达式保存的是词法环境。这也是最重要,也是唯一点来区分这两个概念的原因。
  而要是with语句完全的从ES中消失(下个版本的ES),或正如目前ES5的严格模式下,那么ES概念在这方面的困惑就会少很多。
  再次说明,函数表达式(FE)保存的是词法环境,因为它需要动态绑定在with执行时创建的环境,而函数声明(FD)的保存的变量环境,因为它不能在块级作用域创建以及函数声明提升这一规则。通过下面这个例子来具体了解它们的内部过程:

var a = 10;// FDfunction foo() {  console.log(a);}with ({a: 20}) {  // FE  var bar = function () {    console.log(a);  };  foo(); // 10!, from VariableEnvrionment  bar(); // 20,  from LexicalEnvrionment}foo(); // 10bar(); // still 20它的过程是这样:// "foo" is createdfoo.[[Scope]] = globalContext.[[VariableEnvironment]];// "with" is executedpreviousEnvironment = globalContext.[[LexicalEnvironment]];globalContext.[[LexicalEnvironment]] = {  environmentRecord: {a: 20},  outer: previousEnvironment};// "bar" is createdbar.[[Scope]] = globalContext.[[LexicalEnvironment]];// "with" is completed, restore the environmentglobalContext.[[LexicalEnvironment]] = previousEnvironment;

  为了更清楚的了解这个过程,我们来写些不怎么符合规范的代码,把函数声明移到块中,要记住这是个语法错误。但其实在今天所有的引擎实现都不会抛出错误,而是有一套自己的机制来管理这种情况。Firefox 就有一套非标准的扩展实现叫做函数语句(FS)。其他实现,比如google的V8,会有下面的表现。

var a = 10;with ({a: 20}) {  // FD  function foo() { // do not test in Firefox!    console.log(a);  }  // FE  var bar = function () {    console.log(a);  };  foo(); // 10!, from VariableEnvrionment  bar(); // 20,  from LexicalEnvrionment}foo(); // 10bar(); // still 20

  这里同样是上面提到的情况,函数声明(这里存在声明提升)保存变量环境作为[[scope]]的值,而函数表达式保存的with执行时被替换后的那个词法环境,即with创建的环境。
  注意:ES6中规范了块级函数声明,所以在上面这个例子中,foo也会采用词法环境,输出20。
  虽然我们从抽象的角度讨论了两种环境,但并没有一个严格意义上的区分。所以我们可能会经常说执行上下文的词法环境。
  然而,有一点需要注意,词法环境会参与标识符解析的过程。

原文链接

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