JavaScript作用域链其三:作用域链特征

作用域链相关的一些重要特征
服务器君一共花费了222.141 ms进行了5次数据库查询,努力地为您提供了这个页面。
试试阅读模式?希望听取您的建议

让我们看看与作用域链和函数[[scope]]属性相关的一些重要特征。

闭包

在ECMAScript中,闭包与函数的[[scope]]直接相关,正如我们提到的那样,[[scope]]在函数创建时被存储,与函数共存亡。实际上,闭包是函数代码和其[[scope]]的结合。因此,作为其对象之一,[[Scope]]包括在函数内创建的词法作用域(父变量对象)。当函数进一步激活时,在变量对象的这个词法链(静态的存储于创建时)中,来自较高作用域的变量将被搜寻。例如:

var x = 10;
 
function foo() {
  alert(x);
}
 
(function () {
  var x = 20;
  foo(); // 10, but not 20
})();

我们再次看到,在标识符解析过程中,使用函数创建时定义的词法作用域--变量解析为10,而不是30。此外,这个例子也清晰的表明,一个函数(这个例子中为从函数“foo”返回的匿名函数)的[[scope]]持续存在,即使是在函数创建的作用域已经完成之后。

关于ECMAScript中闭包的理论和其执行机制的更多细节以后会讲解。

通过构造函数创建的函数的[[scope]]

在上面的例子中,我们看到,在函数创建时获得函数的[[scope]]属性,通过该属性访问到所有父上下文的变量。但是,这个规则有一个重要的例外,它涉及到通过函数构造函数创建的函数。

var x = 10;
 
function foo() {
 
  var y = 20;
 
  function barFD() { // 函数声明
    alert(x);
    alert(y);
  }
 
  var barFE = function () { // 函数表达式
    alert(x);
    alert(y);
  };
 
  var barFn = Function('alert(x); alert(y);');
 
  barFD(); // 10, 20
  barFE(); // 10, 20
  barFn(); // 10, "y" is not defined
 
}
 
foo();

我们看到,通过函数构造函数(Function constructor)创建的函数“bar”,是不能访问变量“y”的。但这并不意味着函数“barFn”没有[[scope]]属性(否则它不能访问到变量“x”)。问题在于通过函构造函数创建的函数的[[scope]]属性总是唯一的全局对象。考虑到这一点,如通过这种函数创建除全局之外的最上层的上下文闭包是不可能的。

二维作用域链查找

在作用域链中查找最重要的一点是变量对象的属性(如果有的话)须考虑其中--源于ECMAScript 的原型特性。如果一个属性在对象中没有直接找到,查询将在原型链中继续。即常说的二维链查找。(1)作用域链环节;(2)每个作用域链--深入到原型链环节。如果在Object.prototype 中定义了属性,我们能看到这种效果。

function foo() {
  alert(x);
}
 
Object.prototype.x = 10;
 
foo(); // 10

活动对象没有原型,我们可以在下面的例子中看到:

function foo() {
 
  var x = 20;
 
  function bar() {
    alert(x);
  }
 
  bar();
}
 
Object.prototype.x = 10;
 
foo(); // 20

如果函数“bar”上下文的激活对象有一个原型,那么“x”将在Object.prototype 中被解析,因为它在AO中不被直接解析。但在上面的第一个例子中,在标识符解析中,我们到达全局对象(在一些执行中并不全是这样),它从Object.prototype继承而来,响应地,“x”解析为10。

同样的情况出现在一些版本的SpiderMokey 的命名函数表达式(缩写为NFE)中,在那里特定的对象存储从Object.prototype继承而来的函数表达式的可选名称,在Blackberry中的一些版本中,执行时激活对象从Object.prototype继承。

全局和eval上下文中的作用域链

这里不一定很有趣,但必须要提示一下。全局上下文的作用域链仅包含全局对象。代码eval的上下文与当前的调用上下文(calling context)拥有同样的作用域链。

globalContext.Scope = [
  Global
];
 
evalContext.Scope === callingContext.Scope;

代码执行时对作用域链的影响

在ECMAScript 中,在代码执行阶段有两个声明能修改作用域链。这就是with声明和catch语句。它们添加到作用域链的最前端,对象须在这些声明中出现的标识符中查找。如果发生其中的一个,作用域链简要的作如下修改:

Scope = withObject|catchObject + AO|VO + [[Scope]]

在这个例子中添加对象,对象是它的参数(这样,没有前缀,这个对象的属性变得可以访问)。

var foo = {x: 10, y: 20};
 
with (foo) {
  alert(x); // 10
  alert(y); // 20
}

作用域链修改成这样:

Scope = foo + AO|VO + [[Scope]]

我们再次看到,通过with语句,对象中标识符的解析添加到作用域链的最前端:

var x = 10, y = 10;
 
with ({x: 20}) {
 
  var x = 30, y = 30;
 
  alert(x); // 30
  alert(y); // 30
}
 
alert(x); // 10
alert(y); // 30

在进入上下文时发生了什么?标识符“x”和“y”已被添加到变量对象中。此外,在代码运行阶段作如下修改:

  1. x = 10, y = 10;
  2. 对象{x:20}添加到作用域的前端;
  3. 在with内部,遇到了var声明,当然什么也没创建,因为在进入上下文时,所有变量已被解析添加;
  4. 在第二步中,仅修改变量“x”,实际上对象中的“x”现在被解析,并添加到作用域链的最前端,“x”为20,变为30;
  5. 同样也有变量对象“y”的修改,被解析后其值也相应的由10变为30;
  6. 此外,在with声明完成后,它的特定对象从作用域链中移除(已改变的变量“x”--30也从那个对象中移除),即作用域链的结构恢复到with得到加强以前的状态。
  7. 在最后两个alert中,当前变量对象的“x”保持同一,“y”的值现在等于30,在with声明运行中已发生改变。

同样,catch语句的异常参数变得可以访问,它创建了只有一个属性的新对象--异常参数名。图示看起来像这样:

try {
  ...
} catch (ex) {
  alert(ex);
}

作用域链修改为:

var catchObject = {
  ex: <exception object>
};
 
Scope = catchObject + AO|VO + [[Scope]]

在catch语句完成运行之后,作用域链恢复到以前的状态。

结论

在这个阶段,我们几乎考虑了与执行上下文相关的所有常用概念,以及与它们相关的细节。按照计划--函数对象的详细分析:函数类型(函数声明,函数表达式)和闭包。顺便说一下,在这篇文章中,闭包直接与[[scope]]属性相关,但是,关于它将在合适的篇章中讨论。

延伸阅读

此文章所在专题列表如下:

  1. 我们应该如何去了解JavaScript引擎的工作原理
  2. JavaScript探秘:编写可维护的代码的重要性
  3. JavaScript探秘:谨慎使用全局变量
  4. JavaScript探秘:var预解析与副作用
  5. JavaScript探秘:for循环(for Loops)
  6. JavaScript探秘:for-in循环(for-in Loops)
  7. JavaScript探秘:Prototypes强大过头了
  8. JavaScript探秘:eval()是“魔鬼”
  9. JavaScript探秘:用parseInt()进行数值转换
  10. JavaScript探秘:基本编码规范
  11. JavaScript探秘:函数声明与函数表达式
  12. JavaScript探秘:命名函数表达式
  13. JavaScript探秘:调试器中的函数名
  14. JavaScript探秘:JScript的Bug
  15. JavaScript探秘:JScript的内存管理
  16. JavaScript探秘:SpiderMonkey的怪癖
  17. JavaScript探秘:命名函数表达式替代方案
  18. JavaScript探秘:对象Object
  19. JavaScript探秘:原型链 Prototype chain
  20. JavaScript探秘:构造函数 Constructor
  21. JavaScript探秘:可执行的上下文堆栈
  22. 执行上下文其一:变量对象与活动对象
  23. 执行上下文其二:作用域链 Scope Chains
  24. 执行上下文其三:闭包 Closures
  25. 执行上下文其四:This指针
  26. JavaScript探秘:强大的原型和原型链
  27. JavaScript函数其一:函数声明
  28. JavaScript函数其二:函数表达式
  29. JavaScript函数其三:分组中的函数表达式
  30. JavaScript函数其四:函数构造器
  31. JavaScript变量对象其一:VO的声明
  32. JavaScript变量对象其二:VO在不同的执行上下文中
  33. JavaScript变量对象其三:执行上下文的两个阶段
  34. JavaScript变量对象其四:关于变量
  35. JavaScript变量对象其五:__parent__ 属性
  36. JavaScript作用域链其一:作用域链定义
  37. JavaScript作用域链其二:函数的生命周期
  38. JavaScript作用域链其三:作用域链特征
  39. JavaScript闭包其一:闭包概论
  40. JavaScript闭包其二:闭包的实现
  41. JavaScript闭包其三:闭包的用法

本文地址:http://www.nowamagic.net/librarys/veda/detail/1677,欢迎访问原出处。

不打个分吗? 还木有人打分噢!

转载随意,但请带上本文地址:

http://www.nowamagic.net/librarys/veda/detail/1677

如果你认为这篇文章值得更多人阅读,欢迎使用下面的分享功能。
小提示:您可以按快捷键 Ctrl + D,或点此 加入收藏

阅读一百本计算机著作吧,少年

很多人觉得自己技术进步很慢,学习效率低,我觉得一个重要原因是看的书少了。多少是多呢?起码得看3、4、5、6米吧。给个具体的数量,那就100本书吧。很多人知识结构不好而且不系统,因为在特定领域有一个足够量的知识量+足够良好的知识结构,系统化以后就足以应对大量未曾遇到过的问题。

奉劝自学者:构建特定领域的知识结构体系的路径中再也没有比学习该专业的专业课程更好的了。如果我的知识结构体系足以囊括面试官的大部分甚至吞并他的知识结构体系的话,读到他言语中的一个词我们就已经知道他要表达什么,我们可以让他坐“上位”毕竟他是面试官,但是在知识结构体系以及心理上我们就居高临下。

所以,阅读一百本计算机著作吧,少年!

《Python学习手册(第4版)》 鲁特兹(Mark Lutz) (作者), 李军 (译者), 刘红伟 (译者), 等 (译者)

《Python学习手册(第4版)》学习Python的主要内建对象类型:数字、列表和字典。使用Python语句创建和处理对象,并且学习Python的通用语法模型。使用函数构造和重用代码,函数是Python的基本过程工具。学习Python模块:封装语句、函数以及其他工具,以便构建较大的组件。学习Python的面向对象编程工具,用于组织程序代码。学习异常处理模型,以及用于编写较大程序的开发工具。了解高级Python工具,如装饰器、描述器、元类和Unicode处理等。

更多计算机宝库...