声明:本文的原作者对本文作了修改,本文中对“Feature of implementations: property __parent__”的翻译来自Justin的blog,新版本的翻译请参考Justin的blog:http://www.cnblogs.com/justinw/archive/2010/04/23/1718733.html
导言
在程序中我们总要声明变量和函数,然后成功的用它们来构建我们的系统。当我们引用需要的对象时,解释器如何、在哪里找到我们的数据(functions,variable),会发生什么?
很多ECMAScript 程序员都清楚变量与执行上下文密切相关。
1.
2.
3.
4.
5.
6.
同时,很多程序员也知道,在当前版本的规范中,独立的作用域只能通过“function”代码类型的执行上下文来创建。也就是说,对比C/C++,ECMAScript 中的循环块不能创建一个局部的上下文。
1.
2.
3.
4.
当声明数据时会发生什么?让我们看看更多细节。
数据声明
当变量与执行上下文相关,它应该知道数据存储在哪里,如何得到它。这种机制称之为变量对象。
变量对象(缩写为VO)是与执行上下文相关的对象,它存储:
在上下文中声明 |
例如,它可以表现为正常的ECMAScript对象的变量对象:
1.
正如我们所说,VO是执行上下文的属性:
1.
2.
3.
4.
5.
间接引用变量(通过VO的属性名)只限于全局上下文的变量对象(在那里全局对象自身就是变量对象,稍后将涉及到)。对于其它作用域,直接引用一个VO是不可能的,它完全是执行机制。
当我们声明一个变量或一个函数时,除此之外,还用我们变量的名字和值为VO创建一个新的属性。
例如:
1.
2.
3.
4.
5.
相应的变量对象是:
01.
02.
03.
04.
05.
06.
07.
08.
09.
10.
11.
但是,在执行期间(和规范中),变量对象是一个抽象的实体。从根本来讲,在具体的执行上下文中,VO命名不同,并且有不同的初始结构。
不同执行上下文中的变量对象
当一个变量对象是由实体的子元素构成,并且在不同的执行上下文中是“inherited”的关系时,将变量对象表现为基础、抽象的元素是非常容易的。
全局上下文中的变量对象
在这里,首先有必要给全局对象一个定义:
全局对象是一个在进入任何执行上下文之前创建的对象,该对象存在于单拷贝中,它的属性在程序中的任何位置都可访问,其生命周期结束于程序完成时。
在创建时,全局对象通过一些属性如Math, String, Date, parseInt等初始化。也可以附加其它对象,其中可以引用全局对象本身。例如,在DOM中,全局对象的window属性指向全局对象(但不是在所有的执行中)。
1.
2.
3.
4.
5.
6.
7.
当引用全局对象的属性时,前缀通常省略。因为全局对象不能直接通过名字访问。但是,为了访问它,通过全局对象的this是可能的,也可以通过递归引用自身。例如,DOM中的window,因此可以简写为:
1.
2.
3.
4.
回到全局上下文中的变量对象上来--这里,变量对象是全局对象本身。
1.
有必要准确的理解这一事实,既然是在全局上下文中声明一个变量,我们就能通过全局对象的属性间接的引用它(例如当变量名事先未知时)。
1.
2.
3.
4.
5.
6.
函数上下文中的变量对象
关于函数执行上下文--VO是不能直接访问的,它扮演的角色称之为激活对象(缩写为AO)。
1.
激活对象在进入一个函数的执行上下文中创建,通过属性参数初始化,其值为参数对象(Arguments )。
1.
2.
3.
Arguments 对象是函数执行上下文中激活对象中的一个,包含下列属性:
- callee --当前函数的引用;
- length --真正传递的参数个数;
- properties-indexes --(整数,转换成字符串),其值是函数参数的值。这些properties-indexes 的个数 == arguments.length。Arguments对象的properties-indexes值和呈现的形参(真正传递的)共享。
例如:
01.
02.
03.
04.
05.
06.
07.
08.
09.
10.
11.
12.
13.
14.
15.
16.
17.
18.
在最后一个例子中,Google Chrome的当前版本有一个bug,参数z和arguments[2]是共享的。
上下文代码处理分期
现在,我们已经进入到文章的主要部分。 代码的执行上下文处理分为两个基本阶段:
- 进入执行上下文;
- 代码执行。
变量对象的修改与这两个阶段密切相关。
进入执行上下文
在进入执行上下文(但是在代码执行前)时,VO填充以下属性(它们从一开始就已经被描述)。
- 一个函数的每一个形式参数(如果我们在函数执行上下文中);--变量对象的一个属性被创建,该属性拥有形式参数的名称和值。对于没有传递的参数,变量对象创建一个属性,属性的名称为形式参数的名称,值为undefined。
- 对每个函数声明(FunctionDeclaration, FD)--变量对象的一个属性被创建,该属性拥有函数对象的名字和值。如果变量对象已经包含相同名称的属性,则取代它的值和属性。
- 对每个变量声明(var, VariableDeclaration)--变量对象的一个属性被创建,属性拥有变量的名称,值为undefined。如果变量的名称与已经定义的形参或函数的名称相同,变量声明不会干扰已经存在的属性。
让我们看一个例子:
1.
2.
3.
4.
5.
6.
7.
进入到“test”函数上下文中,传递的参数是10,AO如下:
1.
2.
3.
4.
5.
6.
7.
注意,AO并不包含函数“x”,这是因为“x”不是一个函数声明,而是一个函数表达式(FunctionExpression 缩写为FE),它不影响VO。但是,函数“_e”也是函数表达式。正如我们下面看到的那样,因为它分配给了变量“e”,因此可以通过名称“e”来访问。关于FunctionDeclaration 和FunctionExpression 的详细分歧在Chapter 5. Functions 中讨论。
之后,进入上下文代码处理的第二个阶段--代码执行阶段。
代码执行
此时,AO/VO 已经被属性填满(虽然,它们中的大多数并不拥有我们真正传递的值,大多数还是初始的undefined值)。
就同一个例子而言,在代码解释期间,AO/VO作如下修改:
1.
2.
我再次注意到FunctionExpression “_e” 仍在内存中,只因为它存储在声明的变量“e”中,但是,FunctionExpression “x” 不在AO/VO 中。也就是说,如果我们试着在定义之前或之后调用“x”,将出现错误“x is not defined ”。未保存的FunctionExpression只在它的定义中或递归中调用。
还有一个(经典)的例子:
1.
2.
3.
4.
5.
6.
为什么第一个alert(x)是“function”,而且在声明之前可以访问。为什么不是10或20?因为根据规则--在进入上下文时,VO填充函数声明。在同一个阶段,进入上下文时有一个变量“x”。但正如我们上面提到的步骤所说,变量声明语义上跟在函数声明和参数声明之后,在这个阶段,它不会干扰已经声明的同一名称的函数或形参,因此,在进入上下文中,VO作如下填充:
1.
2.
3.
4.
5.
6.
7.
8.
然后在代码执行阶段,VO作如下修改:
1.
2.
我们可以看到第二个和第三个alert。
在下面的例子中,我们再次看到在进入上下文时变量放入到VO中(else代码块永远不会执行,但即使如此,变量“b”在VO中存在)。
1.
2.
3.
4.
5.
6.
7.
关于变量
通常各类文章,甚至是关于JavaScript的书籍都声称:用var关键字(在全局上下文中)和不用var关键字(在任何地方)声明一个变量是可能的。请记住,并非如此。
变量只使用var关键字来声明。
像这样分配:
1.
仅仅创建全局对象新的属性(而不是变量)。从意义上将,“Not the variable”不是说它不能被改变,而是指ECMAScript变量概念中的“Not the variable”(它成为全局对象的属性,因为VO(globalContext) === global,我们记得,是吧?)
不同之处如下(让我们在例子中展示):
1.
2.
3.
4.
再次取决于VO和它修改的阶段(进入上下文阶段和代码执行阶段)。
进入上下文:
1.
2.
3.
在这个阶段,我们可以看到没有变量“b”,因为它不是一个变量。“b”仅出现在代码执行阶段(既然是一个错误,在我们的例子中就不会出现)。
让我们改变代码:
1.
2.
3.
4.
5.
关于变量有最重要的一点。与简单的属性相比,变量有一个属性{DontDelete} ,即它不可能通过delete运算符删除属性。(关于delete,在我翻译的《理解delete》中有详细分析)
1.
2.
3.
4.
5.
6.
7.
8.
但是,有一个执行上下文对该规则并不生效,那就是eval 上下文:并不为变量设置{DontDelete} 属性。
1.
2.
3.
4.
在一些调试工具的控制台中测试该例子,如Firebug。注意,Firebug 也是用eval来执行你在控制台中的代码。因此,var 不具有{DontDelete} 属性,可以被删除。
特殊实现:property __parent__
前面已经指出,直接访问激活对象是不可能的。但是,在一些工具中,如SpiderMonkey和Rhino。函数有特别的属性__parent__,它是这些函数已经创建的激活对象(或者全局变量对象)的引用。
例如(SpiderMonkey,Rhino):
1.
2.
3.
4.
5.
6.
7.
在上面的例子中,我们看到,函数foo 在全局上下文中创建,因此,它的__parent__ 设置为全局上下文中的变量对象,即全局对象。
但是,在SpiderMonkey 中用同样的方式获得激活对象是不可能的:取决于不同的版本,内部函数的__parent__ 的要么返回null,要么返回全局对象。
在Rhino中,可以获取激活对象,可用同样的方式。
例如(Rhino):
01.
02.
03.
04.
05.
06.
07.
08.
09.
10.
11.
12.
13.
14.
15.
16.
结论
在这篇文章中,我么进一步推进与执行上下文相关对象的研究。我希望它是有用的,能澄清一些你以前有过的问题或模糊之处。按照计划,接下来的章节将讨论Scope chain,Identifier resolution ,Closures 。
如果你有问题,我会很乐意的在评论中回答它们。
其它参考
- 10.1.3 – Variable Instantiation;
- 10.1.5 – Global Object;
- 10.1.6 – Activation Object;
- 10.1.8 – Arguments Object.
转载地址:http://www.denisdeng.com/?p=878
原文地址: ECMA-262-3 in detail. Chapter 2. Variable object.
第一章参考地址:ECMA-262-3 深入解析.第一章.执行上下文