快捷搜索:

JavaScript ECMA-262-3 深入解析(二):变量对象实例

JavaScriptECMA-262-3深入解析(二):变量对象实例详解

  深孔钻编程软件手机编程软件html本文实例讲述了JavaScript ECMA-262-3变量对象。分享给大家供大家参考,具体如下:

  我们在创建应用程序的时候,总免不了要声明变量和函数。那么,当我们需要使用这些东西的时候,解释器(interpreter)是怎么样、从哪里找到我们的数据(函数,变量)的,这个过程究竟发生了什么呢?

  同样,很多程序员也知道,基于当前版本的规范,独立作用域只能通过“函数(function)”代码类型的执行上下文创建。那么,想对于C/C++举例来说,ECMAScript里,for循环并不能创建一个局部的上下文。(译者注:就是局部作用域):

  如果变量与执行上下文相关,那么它自己应该知道它的数据存储在哪里和如何访问。这种机制被称作变量对象(variable object).

  变量对象 (缩写为VO)就是与执行上下文相关的对象(译者注:这个“对象”的意思就是指某个东西),它存储下列内容:

  只有全局上下文的变量对象允许通过VO的属性名称间接访问(因为在全局上下文里,全局对象自身就是变量对象,稍后会详细介绍)。在其它上下文中是不可能直接访问到VO的,因为变量对象完全是实现机制内部的事情。

  在具体实现层面(和在规范中)变量对象只是一个抽象的事物。(译者注:这句话翻译的总感觉不太顺溜,欢迎您提供更好的译文。)从本质上说,在不同的具体执行上下文中,VO的名称和初始结构都不同。

  对于所有类型的执行上下文来说,变量对象的一些操作(如变量初始化)和行为都是共通的。从这个角度来看,把变量对象作为抽象的基本事物来理解更容易。而在函数上下文里同样可以通过变量对象定义一些相关的额外细节。

  全局对象(Global object)是在进入任何执行上下文之前就已经创建的对象;这个对象只存在一份,它的属性在程序中任何地方都可以访问,全局对象的生命周期终止于程序退出那一刻。

  初始创建阶段,全局对象通过Math,String,Date,parseInt等属性初始化,同样也可以附加其它对象作为属性,其中包括可以引用全局对象自身的对象。例如,在DOM中,全局对象的window属性就是引用全局对象自身的属性(当然,并不是所有的具体实现都是这样):

  因为全局对象是不能通过名称直接访问的,所以当访问全局对象的属性时,通常忽略前缀。尽管如此,通过全局上下文的this还是有可能直接访问到全局对象的,同样也可以通过引用自身的属性来访问,例如,DOM中的window。综上所述,代码可以简写为:

  准确理解“全局上下文中的变量对象就是全局对象自身”是非常必要的,基于这个事实,在全局上下文中声明一个变量时,我们才能够通过全局对象的属性间接访问到这个变量(例如,当事先未知变量名时):

  在函数执行上下文中,VO是不能直接访问的,此时由激活对象(activation object,缩写为AO)扮演VO的角色。

  激活对象是在进入函数上下文时刻被创建的,它通过函数的arguments属性初始化。grguments属性的值是Arguments object:

  最后一个例子的场景,在当前版本的Google Chrome浏览器里有一个bug — 即使没有传递参数z,z和arguments[2]仍然是共享的。(译者注:我试验了一下,在Chrome Ver4.1.249.1059版本,该bug仍然存在)

  — 变量对象的一个属性,这个属性由一个形式参数的名称和值组成;如果没有对应传递实际参数,那么这个属性就由形式参数的名称和undefined值组成;

  —变量对象的一个属性,这个属性由一个函数对象(function-object)的名称和值组成;如果变量对象已经存在相同名称的属性,则完全替换这个属性。

  —变量对象的一个属性,这个属性由变量名称和undefined值组成;如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。

  注意,AO里并不包含函数“x”。这是因为“x”是一个函数表达式(FunctionExpression, 缩写为 FE)而不是函数声明,函数表达式不会影响VO(译者注:这里的VO指的就是AO)。 不管怎样,函数“_e”同样也是函数表达式,但是就像我们下面将看到的那样,因为它分配给了变量 “e”,所以它变成可以通过名称“e”来访问。FunctionDeclaration与FunctionExpression的不同,将在Chapter 5. Functions进行详细的探讨。

  这一刻,AO/VO 已经被属性(不过,并不是所有的属性都有值,大部分属性的值还是系统默认的初始值undefined)填满。

  再次注意,因为FunctionExpression“_e”保存到了已声明的变量“e”上,所以它仍然存在于内存中(译者注:就是还在AO/VO中的意思)。而FunctionExpression。未保存的函数表达式只有在它自己的定义或递归中才能被调用。 “x” 并不存在于AO/VO中。即,如果我们想尝试调用“x”函数,不管在函数定义之前还是之后,都会出现一个错误“x is not defined”

  为什么第一个alert “x” 的返回值是function,而且它还是在“x” 声明之前访问的“x” 的?为什么不是10或20呢?因为,根据规范 — 当进入上下文时,往VO里填入函数声明;在相同的阶段,还有一个变量声明“x”,那么正如我们在上一个阶段所说,变量声明在顺序上跟在函数声明和形式参数声明之后,而且,在这个阶段(译者注:这个阶段是指进入执行上下文阶段),变量声明不会干扰VO中已经存在的同名函数声明或形式参数声明,因此,在进入上下文时,VO的结构如下:

  在下面的例子里我们可以再次看到,变量是在进入上下文阶段放入VO中的。(因为,虽然else部分代码永远不会执行,但是不管怎样,变量“b”仍然存在于VO中。)(译者注:变量b虽然存在于VO中,但是变量b的值永远是undefined)

  通常,各类文章和JavaScript相关的书籍都声称:“不管是使用var关键字(在全局上下文)还是不使用var关键字(在任何地方),都可以声明一个变量”。请记住,这绝对是谣传:

  这仅是给全局对象创建了一个新属性(但是它不是变量)。“不是变量”的意思并不是说它不能被改变,而是指它不符合ECMAScript规范中的变量概念,所以它“不是变量”(它之所以能成为全局对象的属性,完全是因为VO(globalContext) === global,大家还记得这个吧?)。

  我们可以看到,因为“b”不是一个变量,所以在这个阶段根本就没有“b”,“b”将只在执行代码阶段才会出现(但是在我们这个例子里,还没有到那就已经出错了)。

  关于变量,还有一个重要的知识点ASP编程。变量相对于简单属性来说,变量有一个特性(attribute):{DontDelete },这个特性的含义就是不同通过delete操作符直接删除变量属性。

  但是,在eval上下文,这个规则并不起作用,因为在这个上下文里,变量没有{DontDelete } 特性。

  使用一些调试工具(例如:Firebug)的控制台测试该实例时,请注意,Firebug同样是使用eval来执行控制台里你的代码。因此,变量属性同样没有{DontDelete }特性,可以被删除。

  前面已经提到过,按标准规范,激活对象是不可能被直接访问到的。但是,一些具体实现并没有完全遵守这个规定,例如SpiderMonkey和Rhino;在这些具体实现中,函数有一个特殊的属性__parent__,通过这个属性可以直接引用到函数已经创建的激活对象或全局变量对象。

  在上面的例子中我们可以看到,函数foo是在全局上下文中创建的,所以属性__parent__指向全局上下文的变量对象,即全局对象。(译者注:还记得这个吧:VO(globalContext) === global)

  然而,在SpiderMonkey中用同样的方式访问激活对象是不可能的:在不同版本的SpiderMonkey中,内部函数的__parent__有时指向null,有时指向全局对象。

  在这篇文章里,我们进一步深入学习了跟执行上下文相关的对象。我希望这些知识对您来说能有所帮助,能解决一些您曾经遇到的问题或困惑。按照计划,在后续的章节中,我们将探讨Scope chain,Identifier resolution,Closures。

  感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:测试上述代码运行效果。

  更多关于JavaScript相关内容可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》

您可能还会对下面的文章感兴趣: