资讯专栏INFORMATION COLUMN

Lexical environments: ECMAScript implementation

roadtogeek / 2820人阅读

摘要:全局环境是作用域链的终点。环境记录类型定义了两种环境记录类型声明式环境记录和对象环境记录声明式环境记录声明式环境记录是用来处理函数作用域中出现的变量,函数,形参等。变量环境变量环境就是存储上下文中的变量和函数的。解析的过程如下

原文

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)相对应。因此,一条环境记录记录这个词法环境中创建的标识符的绑定。换句话说,一条环境记录保存了出现在上下文中的变量。
考虑下面这个例子

</>复制代码

  1. var x = 10;
  2. function foo() {
  3. var y = 20;
  4. }

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

</>复制代码

  1. // environment of the global context
  2. globalEnvironment = {
  3. environmentRecord: {
  4. // built-ins:
  5. Object: function,
  6. Array: function,
  7. // etc ...
  8. // our bindings:
  9. x: 10
  10. },
  11. outer: null // no parent environment
  12. }
  13. // environment of the "foo" function
  14. fooEnvironment of the "foo" function
  15. fooEnvironment = {
  16. environmentRecord: {
  17. y: 20
  18. },
  19. outer: globalEnvironment
  20. }

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

</>复制代码

  1. function foo() {
  2. var x = 10;
  3. function bar() {
  4. var y = 20;
  5. console.log(x + y); // 30
  6. }
  7. function baz() {
  8. var z = 30;
  9. console.log(x + z); // 40
  10. }
  11. }
  12. // ----- Environments -----
  13. // "foo" environment
  14. fooEnvironment = {
  15. environmentRecord: {x: 10},
  16. outer: globalEnvironment
  17. };
  18. // both "bar" and "baz" have the same outer
  19. // environment -- the environment of "foo"
  20. barEnvironment = {
  21. environmentRecord: {y: 20},
  22. outer: fooEnvironment
  23. };
  24. bazEnvironment = {
  25. environmentRecord: {z: 30},
  26. outer: fooEnvironment
  27. }
环境记录类型

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

声明式环境记录

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

</>复制代码

  1. // all: "a", "b" and "c"
  2. // bindings are bindings of
  3. // a declarative record
  4. function foo(a) {
  5. var b = 10;
  6. function c() {}
  7. }

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

</>复制代码

  1. 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”.

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

</>复制代码

  1. environment = {
  2. // storage
  3. environmentRecord: {
  4. type: "declarative",
  5. // storage
  6. },
  7. // reference to the parent environment
  8. outer: <...>
  9. };
对象环境记录

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

</>复制代码

  1. var a = 10;
  2. console.log(a); // 10
  3. // "this" in the global context
  4. // is the global object itself
  5. console.log(this.a); // 10

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

</>复制代码

  1. environment = {
  2. // storage
  3. environmentRecord: {
  4. type: "object",
  5. bindingObject: {
  6. // storage
  7. }
  8. },
  9. // reference to the parent environment
  10. outer: <...>
  11. };
执行环境的结构

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

</>复制代码

  1. ExecutionContextES5 = {
  2. ThisBinding: ,
  3. VariableEnvironment: { ... },
  4. LexicalEnvironment: { ... },
  5. }
this绑定

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

</>复制代码

  1. (function (global) {
  2. global.a = 10;
  3. })(this);
  4. console.log(a); // 10

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

</>复制代码

  1. var foo = {
  2. bar: function () {
  3. console.log(this);
  4. }
  5. };
  6. // --- Reference cases ---
  7. // with a reference
  8. foo.bar(); // "this" is "foo" - the base
  9. var bar = foo.bar;
  10. // with the reference
  11. bar(); // "this" is the global, implicit base
  12. this.bar(); // the same, explicit base, the global
  13. // with also but another reference
  14. bar.prototype.constructor(); // "this" is "bar.prototype"
变量环境

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

</>复制代码

  1. function foo(a) {
  2. var b = 20;
  3. }
  4. foo(10);

它的变量环境

</>复制代码

  1. fooContext.VariableEnvironment = {
  2. environmentRecord: {
  3. arguments: {0: 10, length: 1, callee: foo},
  4. a: 10,
  5. b: 20
  6. },
  7. outer: globalEnvironment
  8. };
词法环境

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

标识符解析

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

</>复制代码

  1. var a = 10;
  2. (function foo() {
  3. var b = 20;
  4. (function bar() {
  5. var c = 30;
  6. console.log(a + b + c); // 60
  7. })();
  8. })();

解析a的过程如下

</>复制代码

  1. function resolveIdentifier(lexicalEnvironment, identifier) {
  2. // if it"s the final link, and we didn"t find
  3. // anything, we have a case of a reference error
  4. if (lexicalEnvironment == null) {
  5. throw ReferenceError(identifier + " is not defined");
  6. }
  7. // return the binding (reference) if it exists;
  8. // later we"ll be able to get the value from the reference
  9. if (lexicalEnvironment.hasBinding(identifier)) {
  10. return new Reference(lexicalEnvironment, identifier);
  11. }
  12. // else try to find in the parent scope,
  13. // recursively analyzing the outer environment
  14. return resolveIdentifier(lexicalEnvironment.outer, identifier);
  15. }
  16. resolveIdentifier(bar.[[LexicalEnvironment]], "a") ->
  17. -- bar.[[LexicalEnvironment]] - not found,
  18. -- bar.[[LexicalEnvironment]].outer (i.e. foo.[[LexicalEnvironment]]) -> not found
  19. -- bar.[[LexicalEnvironment]].outer.outer -> found reference, value 10

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/89588.html

相关文章

  • Lexical environments: Common Theory

    摘要:调用栈意味着中的执行环境栈,激活记录意味着的激活对象。此外,所有的函数是一等公民。换句话说,自由变量并不存在自身的环境中,而是周围的环境中。值得注意的是,函数并没有用到自由变量。在后面的情形中,我们将绑定对象称为环境帧。 原文 ECMA-262-5 in detail. Chapter 3.1. Lexical environments: Common Theory. 简介 在这一章,...

    罗志环 评论0 收藏0
  • JavaScript深入之从ECMAScript规范解读this

    摘要:深入系列第六篇,本篇我们追根溯源,从规范解读在函数调用时到底是如何确定的。因为我们要从规范开始讲起。规范类型包括和。下一篇文章深入之执行上下文深入系列深入系列目录地址。如果有错误或者不严谨的地方,请务必给予指正,十分感谢。 JavaScript深入系列第六篇,本篇我们追根溯源,从 ECMAScript5 规范解读 this 在函数调用时到底是如何确定的。 前言 在《JavaScript...

    TIGERB 评论0 收藏0
  • [譯] 透過重新實作來學習參透閉包

    摘要:不過到底是怎麼保留的另外為什麼一個閉包可以一直使用區域變數,即便這些變數在該內已經不存在了為了解開閉包的神秘面紗,我們將要假裝沒有閉包這東西而且也不能夠用嵌套來重新實作閉包。 原文出處: 連結 話說網路上有很多文章在探討閉包(Closures)時大多都是簡單的帶過。大多的都將閉包的定義濃縮成一句簡單的解釋,那就是一個閉包是一個函數能夠保留其建立時的執行環境。不過到底是怎麼保留的? 另外...

    CoXie 评论0 收藏0
  • JavaScript 进阶 从实现理解闭包(校对版)

    摘要:嵌套函数在一个函数中创建另一个函数,称为嵌套。这在很容易实现嵌套函数可以访问外部变量,帮助我们很方便地返回组合后的全名。更有趣的是,嵌套函数可以作为一个新对象的属性或者自己本身被。 来源于 现代JavaScript教程闭包章节中文翻译计划本文很清晰地解释了闭包是什么,以及闭包如何产生,相信你看完也会有所收获 关键字Closure 闭包Lexical Environment 词法环境En...

    wuyangnju 评论0 收藏0
  • 分享一个关于匿名函数和闭包的问答

    摘要:引用一个的提问个人觉得总结的比较好的两句话原文地址另外,附上中对闭包的讲解闭包中文对于闭包的简要概括原文原文地址匿名函数和闭包来自文章作者版权声明自由转载非商用非衍生保持署名创意共享许可证转载请注明出处 引用一个stackoverflow的提问 个人觉得总结的比较好的两句话: An anonymous function is just a function that has no na...

    Barrior 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<