翻译:ECMA-262-3 深入解析.第二章.变量对象

声明:本文的原作者对本文作了修改,本文中对“Feature of implementations: property __parent__”的翻译来自Justin的blog,新版本的翻译请参考Justin的blog:http://www.cnblogs.com/justinw/archive/2010/04/23/1718733.html

  1. 导言
  2. 数据声明
  3. 不同执行上下文中的变量对象
    1. 全局上下文中的变量对象
    2. 函数上下文中的变量对象
  4. 上下文代码处理分期
    1. 进入执行上下文
    2. 代码执行
  5. 关于变量
  6. 工具特征:property __parent__
  7. 结论
  8. 其它参考

导言

在程序中我们总要声明变量和函数,然后成功的用它们来构建我们的系统。当我们引用需要的对象时,解释器如何、在哪里找到我们的数据(functions,variable),会发生什么?

很多ECMAScript 程序员都清楚变量与执行上下文密切相关。

1. var a = 10;// variable of the global context
2. (function () {
3. var b = 20;// local variable of the function context
4. })();
5. alert(a);// 10
6. alert(b);// "b" is not defined

同时,很多程序员也知道,在当前版本的规范中,独立的作用域只能通过“function”代码类型的执行上下文来创建。也就是说,对比C/C++,ECMAScript 中的循环块不能创建一个局部的上下文。

1. for (var in {a: 1, b: 2}) {
2. alert(k);
3. }
4. alert(k);// variable "k" still in scope even the loop is finished

当声明数据时会发生什么?让我们看看更多细节。

数据声明

当变量与执行上下文相关,它应该知道数据存储在哪里,如何得到它。这种机制称之为变量对象。

变量对象(缩写为VO)是与执行上下文相关的对象,它存储:

  • 变量(var VariableDeclaration);
  • 函数声明(FunctionDeclaration 缩写为FD);
  • 以及函数的形参。

在上下文中声明

例如,它可以表现为正常的ECMAScript对象的变量对象:

1. VO = {};

正如我们所说,VO是执行上下文的属性:

1. activeExecutionContext = {
2. VO: {
3. // context data (var, FD, function arguments)
4. }
5. };

间接引用变量(通过VO的属性名)只限于全局上下文的变量对象(在那里全局对象自身就是变量对象,稍后将涉及到)。对于其它作用域,直接引用一个VO是不可能的,它完全是执行机制。

当我们声明一个变量或一个函数时,除此之外,还用我们变量的名字和值为VO创建一个新的属性。

例如:

1. var a = 10;
2. function test(x) {
3. var b = 20;
4. };
5. test(30);

相应的变量对象是:

01. // Variable object of the global context
02. VO(globalContext) = {
03. a: 10,
04. test:<reference to function>
05. };
06.  
07. // Variable object of the "test" function context
08. VO(test functionContext) = {
09. x: 30,
10. b: 20
11. };

但是,在执行期间(和规范中),变量对象是一个抽象的实体。从根本来讲,在具体的执行上下文中,VO命名不同,并且有不同的初始结构。

不同执行上下文中的变量对象

当一个变量对象是由实体的子元素构成,并且在不同的执行上下文中是“inherited”的关系时,将变量对象表现为基础、抽象的元素是非常容易的。

全局上下文中的变量对象

在这里,首先有必要给全局对象一个定义:

全局对象是一个在进入任何执行上下文之前创建的对象,该对象存在于单拷贝中,它的属性在程序中的任何位置都可访问,其生命周期结束于程序完成时。

在创建时,全局对象通过一些属性如Math, String, Date, parseInt等初始化。也可以附加其它对象,其中可以引用全局对象本身。例如,在DOM中,全局对象的window属性指向全局对象(但不是在所有的执行中)。

1. global = {
2. Math: <...>,
3. String: <...>
4. ...
5. ...
6. window: global
7. };

当引用全局对象的属性时,前缀通常省略。因为全局对象不能直接通过名字访问。但是,为了访问它,通过全局对象的this是可能的,也可以通过递归引用自身。例如,DOM中的window,因此可以简写为:

1. String(10);// means global.String(10);
2. // with prefixes
3. window.a = 10;// === global.window.a = 10 === global.a = 10;
4. this.b = 20;// global.b = 20;

回到全局上下文中的变量对象上来--这里,变量对象是全局对象本身。

1. VO(globalContext) === global;

有必要准确的理解这一事实,既然是在全局上下文中声明一个变量,我们就能通过全局对象的属性间接的引用它(例如当变量名事先未知时)。

1. var a = new String('test');
2. alert(a);// directly, is found in VO(globalContext): "test"
3. alert(window['a']);// indirectly via global === VO(globalContext): "test"
4. alert(a === this.a);// true
5. var aKey = 'a';
6. alert(window[aKey]);// indirectly, with dynamic property name: "test"

函数上下文中的变量对象

关于函数执行上下文--VO是不能直接访问的,它扮演的角色称之为激活对象(缩写为AO)。

1. VO(functionContext) === AO;

激活对象在进入一个函数的执行上下文中创建,通过属性参数初始化,其值为参数对象(Arguments )。

1. AO = {
2. arguments: <ArgO>
3. };

Arguments 对象是函数执行上下文中激活对象中的一个,包含下列属性:

  • callee --当前函数的引用;
  • length --真正传递的参数个数;
  • properties-indexes --(整数,转换成字符串),其值是函数参数的值。这些properties-indexes 的个数 == arguments.length。Arguments对象的properties-indexes值和呈现的形参(真正传递的)共享。

例如:

01. function foo(x, y, z) {
02. alert(arguments.length);// 2 – quantity of passed arguments
03. alert(arguments.callee === foo);// true
04. alert(x === arguments[0]);// true
05. alert(x);// 10
06. arguments[0] = 20;
07. alert(x);// 20
08. x = 30;
09. alert(arguments[0]);// 30
10. // however, for not passed argument z,
11. // related index-property of the arguments
12. // object is not shared
13. z = 40;
14. alert(arguments[2]);// undefined
15. arguments[2] = 50;
16. alert(z);// 40
17. }
18. foo(10, 20);

在最后一个例子中,Google Chrome的当前版本有一个bug,参数z和arguments[2]是共享的。

上下文代码处理分期

现在,我们已经进入到文章的主要部分。 代码的执行上下文处理分为两个基本阶段:

  1. 进入执行上下文;
  2. 代码执行。

变量对象的修改与这两个阶段密切相关。

进入执行上下文

在进入执行上下文(但是在代码执行前)时,VO填充以下属性(它们从一开始就已经被描述)。

  • 一个函数的每一个形式参数(如果我们在函数执行上下文中);--变量对象的一个属性被创建,该属性拥有形式参数的名称和值。对于没有传递的参数,变量对象创建一个属性,属性的名称为形式参数的名称,值为undefined。
  • 对每个函数声明(FunctionDeclaration, FD)--变量对象的一个属性被创建,该属性拥有函数对象的名字和值。如果变量对象已经包含相同名称的属性,则取代它的值和属性。
  • 对每个变量声明(var, VariableDeclaration)--变量对象的一个属性被创建,属性拥有变量的名称,值为undefined。如果变量的名称与已经定义的形参或函数的名称相同,变量声明不会干扰已经存在的属性。

让我们看一个例子:

1. function test(a, b) {
2. var c = 10;
3. function d() {}
4. var e = function _e() {};
5. (function x() {});
6. }
7. test(10);// call

进入到“test”函数上下文中,传递的参数是10,AO如下:

1. AO(test) = {
2. a: 10,
3. b: undefined,
4. c: undefined,
5. d: <reference to FunctionDeclaration "d">
6. e: undefined
7. };

注意,AO并不包含函数“x”,这是因为“x”不是一个函数声明,而是一个函数表达式(FunctionExpression 缩写为FE),它不影响VO。但是,函数“_e”也是函数表达式。正如我们下面看到的那样,因为它分配给了变量“e”,因此可以通过名称“e”来访问。关于FunctionDeclaration 和FunctionExpression 的详细分歧在Chapter 5. Functions 中讨论。

之后,进入上下文代码处理的第二个阶段--代码执行阶段。

代码执行

此时,AO/VO 已经被属性填满(虽然,它们中的大多数并不拥有我们真正传递的值,大多数还是初始的undefined值)。

就同一个例子而言,在代码解释期间,AO/VO作如下修改:

1. AO['c'] = 10;
2. AO['e'] = <reference to FunctionExpression "_e">;

我再次注意到FunctionExpression “_e” 仍在内存中,只因为它存储在声明的变量“e”中,但是,FunctionExpression “x” 不在AO/VO 中。也就是说,如果我们试着在定义之前或之后调用“x”,将出现错误“x is not defined ”。未保存的FunctionExpression只在它的定义中或递归中调用。

还有一个(经典)的例子:

1. alert(x);// function
2. var x = 10;
3. alert(x);// 10
4. x = 20;
5. function x() {};
6. alert(x);// 20

为什么第一个alert(x)是“function”,而且在声明之前可以访问。为什么不是10或20?因为根据规则--在进入上下文时,VO填充函数声明。在同一个阶段,进入上下文时有一个变量“x”。但正如我们上面提到的步骤所说,变量声明语义上跟在函数声明和参数声明之后,在这个阶段,它不会干扰已经声明的同一名称的函数或形参,因此,在进入上下文中,VO作如下填充:

1. VO = {};
2. VO['x'] = <treference to FunctionDeclaration "x" >
3. // found var x = 10;
4. // if function "x" would not be already defined
5. // then "x" be undefined, but in our case
6. // variable declaration does not disturb
7. // the value of the function with the same name
8. VO['x'] =<the value is not disturbed, still function >

然后在代码执行阶段,VO作如下修改:

1. VO['x'] = 10;
2. VO['x'] = 20;

我们可以看到第二个和第三个alert。

在下面的例子中,我们再次看到在进入上下文时变量放入到VO中(else代码块永远不会执行,但即使如此,变量“b”在VO中存在)。

1. if (true) {
2. var a = 1;
3. else {
4. var b = 2;
5. }
6. alert(a);// 1
7. alert(b);// undefined, but not "b is not defined"

关于变量

通常各类文章,甚至是关于JavaScript的书籍都声称:用var关键字(在全局上下文中)和不用var关键字(在任何地方)声明一个变量是可能的。请记住,并非如此。

变量只使用var关键字来声明。

像这样分配:

1. a = 10;

仅仅创建全局对象新的属性(而不是变量)。从意义上将,“Not the variable”不是说它不能被改变,而是指ECMAScript变量概念中的“Not the variable”(它成为全局对象的属性,因为VO(globalContext) === global,我们记得,是吧?)

不同之处如下(让我们在例子中展示):

1. alert(a);// undefined
2. alert(b);// "b" is not defined
3. b = 10;
4. var a = 20;

再次取决于VO和它修改的阶段(进入上下文阶段和代码执行阶段)。

进入上下文:

1. VO = {
2. a: undefined
3. };

在这个阶段,我们可以看到没有变量“b”,因为它不是一个变量。“b”仅出现在代码执行阶段(既然是一个错误,在我们的例子中就不会出现)。

让我们改变代码:

1. alert(a);// undefined, we know why
2. b = 10;
3. alert(b);// 10, created at code execution
4. var a = 20;
5. alert(a);// 20, modified at code execution

关于变量有最重要的一点。与简单的属性相比,变量有一个属性{DontDelete} ,即它不可能通过delete运算符删除属性。(关于delete,在我翻译的《理解delete》中有详细分析)

1. a = 10;
2. alert(window.a);// 10
3. alert(delete a);// true
4. alert(window.a);// undefined
5. var b = 20;
6. alert(window.b);// 20
7. alert(delete b);// false
8. alert(window.b);// still 20

但是,有一个执行上下文对该规则并不生效,那就是eval 上下文:并不为变量设置{DontDelete} 属性。

1. eval('var a = 10;');
2. alert(window.a);// 10
3. alert(delete a);// true
4. alert(window.a);// undefined

在一些调试工具的控制台中测试该例子,如Firebug。注意,Firebug 也是用eval来执行你在控制台中的代码。因此,var 不具有{DontDelete} 属性,可以被删除。

特殊实现:property __parent__

前面已经指出,直接访问激活对象是不可能的。但是,在一些工具中,如SpiderMonkey和Rhino。函数有特别的属性__parent__,它是这些函数已经创建的激活对象(或者全局变量对象)的引用。

例如(SpiderMonkey,Rhino):

1. var global = this;
2. var a = 10;
3. function foo() {}
4. alert(foo.__parent__);// global
5. var VO = foo.__parent__;
6. alert(VO.a);// 10
7. alert(VO === global);// true

在上面的例子中,我们看到,函数foo 在全局上下文中创建,因此,它的__parent__ 设置为全局上下文中的变量对象,即全局对象。

但是,在SpiderMonkey 中用同样的方式获得激活对象是不可能的:取决于不同的版本,内部函数的__parent__ 的要么返回null,要么返回全局对象。

在Rhino中,可以获取激活对象,可用同样的方式。

例如(Rhino):

01. var global = this;
02. var x = 10;
03. (function foo() {
04. var y = 20;
05. // the activation object of the "foo" context
06. var AO = (function () {}).__parent__;
07. print(AO.y);// 20
08. // __parent__ of the current activation
09. // object is already the global object,
10. // i.e. the special chain of variable objects is formed,
11. // so-called, a scope chain
12. print(AO.__parent__ === global);// true
13.  
14. print(AO.__parent__.x);// 10
15.  
16. })();

结论

在这篇文章中,我么进一步推进与执行上下文相关对象的研究。我希望它是有用的,能澄清一些你以前有过的问题或模糊之处。按照计划,接下来的章节将讨论Scope chain,Identifier resolution ,Closures 。

如果你有问题,我会很乐意的在评论中回答它们。

其它参考

转载地址:http://www.denisdeng.com/?p=878

原文地址: ECMA-262-3 in detail. Chapter 2. Variable object.

第一章参考地址:ECMA-262-3 深入解析.第一章.执行上下文

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值