`
三月沙
  • 浏览: 618193 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

ECMA-262-3 in detail. Chapter 2. Variable object.

 
阅读更多

 by Dmitry Soshnikov

 

 

 

Always in programs we declare functions and variables which then successfully use building our systems. But how and where the interpreter finds our data (functions, variable)? What occurs, when we reference to needed objects?

Many ECMAScript programmers know that variables are closely related with the execution context:

var a = 10; // variable of the global context
 
(function () {
  var b = 20; // local variable of the function context
})();
 
alert(a); // 10
alert(b); // "b" is not defined

Also, many programmers know that the isolated scope in the current version of specification is created only by execution contexts with “function” code type. I.e., in contrast with C/C++, for example the block of forloop in ECMAScript does not create a local context:

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

Let’s see in more details what occurs when we declare our data.

If variables are related with the execution context, it should know where its data are stored and how to get them. This mechanism is called a variable object.

variable object (in abbreviated form — VO) is a special object related with an execution context and which stores:

 

  • variables (var, VariableDeclaration);
  • function declarations (FunctionDeclaration, in abbreviated form FD);
  • and function formal parameters

declared in the context.

Notice, in ES5 the concept of variable object is replaced with lexical environments model, which detailed description can be found in appropriate chapter.

Schematically and for examples, it is possible to present variable object as a normal ECMAScript object:

VO = {};

And as we said, VO is a property of an execution context:

activeExecutionContext = {
  VO: {
    // context data (var, FD, function arguments)
  }
};

Indirect referencing to variables (via property names of VO) allows only variable object of the global context (where the global object is itself the variable object). For other contexts directly to reference the VO is not possible, it is purely mechanism of implementation.

When we declare a variable or a function, there is nothing else as creation of the new property of the VO with the name and value of our variable.

Example:

var a = 10;
 
function test(x) {
  var b = 20;
};
 
test(30);

And corresponding variable objects are:

// Variable object of the global context
VO(globalContext) = {
  a: 10,
  test: <reference to function>
};
 
// Variable object of the "test" function context
VO(test functionContext) = {
  x: 30,
  b: 20
};

But at implementation level (and specification) the variable object is an abstract essence. Physically, in concrete execution contexts, VO is named differently and has different initial structure.

Some operations (e.g. variable instantiation) and behavior of the variable object are common for all execution context types. From this viewpoint it is convenient to present the variable object as an abstract base thing. Function context can also define additional details related with the variable object.

AbstractVO (generic behavior of the variable instantiation process)
 
  
  ╠══> GlobalContextVO
  ║        (VO === this === global)
  
  ╚══> FunctionContextVO
           (VO === AO, <arguments> object and <formal parameters> are added)

Let’s consider it in detail.

Here, first it is necessary to give definition of the Global object.

Global object is the object which is created before entering any execution context; this object exists in the single copy, its properties are accessible from any place of the program, the life cycle of the global object ends with program end.

At creation the global object is initialized with such properties as MathStringDateparseInt etc., and also by additional objects among which can be the reference to the global object itself — for example, inBOMwindow property of the global object refers to global object (however, not in all implementations):

global = {
  Math: <...>,
  String: <...>
  ...
  ...
  window: global
};

When referencing to properties of global object the prefix is usually omitted, because global object is not accessible directly by name. However, to get access to it is possible via this value in the global context, and also through recursive references to itself, for example window in BOM, therefore write simply:

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

So, coming back to variable object of the global context — here variable object is the global object itself:

VO(globalContext) === global;

It is necessary to understand accurately this fact since for this reason declaring a variable in the global context, we have ability to reference it indirectly via property of the global object (for example when the variable name is unknown in advance):

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

Regarding the execution context of functions — there VO is inaccessible directly, and its role plays so-called an activation object (in abbreviated form — AO).

VO(functionContext) === AO;

An activation object is created on entering the context of a function and initialized by propertyarguments which value is the Arguments object:

AO = {
  arguments: <ArgO>
};

Arguments object is a property of the activation object. It contains the following properties:

  • callee — the reference to the current function;
  • length — quantity of real passed arguments;
  • properties-indexes (integer, converted to string) which values are the values of function’s arguments (from left to right in the list of arguments). Quantity of these properties-indexes == arguments.length. Values of properties-indexes of the arguments object and present (really passed) formal parameters are shared.

Example:

function foo(x, y, z) {
 
  // quantity of defined function arguments (x, y, z)
  alert(foo.length); // 3
 
  // quantity of really passed arguments (only x, y)
  alert(arguments.length); // 2
 
  // reference of a function to itself
  alert(arguments.callee === foo); // true
 
  // parameters sharing
 
  alert(x === arguments[0]); // true
  alert(x); // 10
 
  arguments[0] = 20;
  alert(x); // 20
 
  x = 30;
  alert(arguments[0]); // 30
 
  // however, for not passed argument z,
  // related index-property of the arguments
  // object is not shared
 
  z = 40;
  alert(arguments[2]); // undefined
 
  arguments[2] = 50;
  alert(z); // 40
 
}
 
foo(10, 20);

Concerning the last case, in older versions of Google Chrome there was a bug — there parameter z andarguments[2] were also shared.

In ES5 the concept of activation object is also replaced with common and single model oflexical environments.

Now we have reached the main point of this article. Processing of the execution context code is divided on two basic stages:

  1. Entering the execution context;
  2. Code execution.

Modifications of the variable object are closely related with these two phases.

Notice, that processing of these two stages are the general behavior and independent from the type of the context (i.e. it is fair for bothglobal and function contexts).

On entering the execution context (but before the code execution), VO is filled with the following properties (they have already been described at the beginning):

  • for each formal parameter of a function (if we are in function execution context)
  • — a property of the variable object with a name and value of formal parameter is created; for not passed parameters — property of VO with a name of formal parameter and value undefined is created;

  • for each function declaration (FunctionDeclaration, FD)
  • — a property of the variable object with a name and value of a function-object is created; if the variable object already contains a property with the same name, replace its value and attributes;

  • for each variable declaration (var, VariableDeclaration)
  • — a property of the variable object with a variable name and value undefined is created; if the variable name is the same as a name of already declared formal parameter or a function, the variable declaration does not disturb the existing property.

Let’s see on the example:

function test(a, b) {
  var c = 10;
  function d() {}
  var e = function _e() {};
  (function x() {});
}
 
test(10); // call

On entering the test function context with the passed parameter 10, AO is the following:

AO(test) = {
  a: 10,
  b: undefined,
  c: undefined,
  d: <reference to FunctionDeclaration "d">
  e: undefined
};

Notice, that AO does not contain function x. This is because x is not a function declaration but thefunction-expression (FunctionExpression, in abbreviated form FE) which does not affect on VO.

However, function _e is also a function-expression, but as we will see below, because of assigning it to the variable e, it becomes accessible via the e name. The difference of a FunctionDeclaration from theFunctionExpression is in detail discussed in Chapter 5. Functions.

And after that there comes the second phase of processing of a context code — the code execution stage.

By this moment, AO/VO is already filled by properties (though, not all of them have the real values passed by us, most of them yet still have initial value undefined).

Considering all the same example, AO/VO during the code interpretation is modified as follows:

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

Once again I notice that function expression _e is still in memory only because it is saved to declared variable e. But the function expression x is not in the AO/VO. If we try to call x function before or even after definition, we get an error: "x" is not defined. Unsaved to a variable function expression can be called only with its definition (in place) or recursively.

One more (classical) example:

alert(x); // function
 
var x = 10;
alert(x); // 10
 
x = 20;
 
function x() {}
 
alert(x); // 20

Why in the first alert x is a function and moreover is accessible before the declaration? Why it’s not 10or 20? Because, according to the rule — VO is filled with function declarations on entering the context. Also, at the same phase, on entering the context, there is a variable declaration x, but as we mentioned above, the step of variable declarations semantically goes after function and formal parameters declarations and on this phase do not disturb the value of the already declared function or formal parameter with the same name. Therefore, on entering the context VO is filled as follows:

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

And then at code execution phase, VO is modified as follows:

VO['x'] = 10;
VO['x'] = 20;

what we can see in the second and third alerts.

In the example below we see again that variables are put into the VO on entering the context phase (so, theelse block is never executed, but nevertheless, the variable b exists in VO):

if (true) {
  var a = 1;
} else {
  var b = 2;
}
 
alert(a); // 1
alert(b); // undefined, but not "b is not defined"

Often various articles and even books on JavaScript claim that: “it is possible to declare global variables using var keyword (in the global context) and without using var keyword (in any place)”. It is not so. Remember:

variables are declared only with using var keyword.

And assignments like:

a = 10;

just create the new property (but not the variable) of the global object. “Not the variable” is not in the sense that it cannot be changed, but “not the variable” in concept of variables in ECMAScript (which then also become properties of the global object because of VO(globalContext) === global, we remember, yeah?).

And the difference is the following (let’s show on the example):

alert(a); // undefined
alert(b); // "b" is not defined
 
b = 10;
var a = 20;

All again depends on VO and phases of its modifications (entering the context stage and the code execution stage):

Entering the context:

VO = {
  a: undefined
};

We see that at this phase there is no any b since it is not a variable, b will appear only at code execution phase (but in our case won’t since there is an error).

Let’s change the code:

alert(a); // undefined, we know why
 
b = 10;
alert(b); // 10, created at code execution
 
var a = 20;
alert(a); // 20, modified at code execution

There is one more important point concerning variables. Variables, in contrast with simple properties, have attribute {DontDelete}, meaning impossibility to remove a variable via the delete operator:

a = 10;
alert(window.a); // 10
 
alert(delete a); // true
 
alert(window.a); // undefined
 
var b = 20;
alert(window.b); // 20
 
alert(delete b); // false
 
alert(window.b); // still 20

Note, in ES5 {DontDelete} is renamed into the [[Configurable]] and can be manually managed via Object.defineProperty method.

However there is one execution context on which this rule does not affect. It is the eval context: there{DontDelete} attribute is not set for variables:

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

For those who test these examples in console of some debug tool, e.g. Firebug: notice, that Firebug alsouses eval to execute your code from the console. So there vars also do not have {DontDelete} and can be deleted.

As it was already noted, by the standard, to get direct access to the activation object is impossible. However, in some implementations, namely in SpiderMonkey and Rhino, functions have special property__parent__, which is the reference to the activation object (or the global variable object) in which these functions have been created.

Example (SpiderMonkey, Rhino):

var global = this;
var a = 10;
 
function foo() {}
 
alert(foo.__parent__); // global
 
var VO = foo.__parent__;
 
alert(VO.a); // 10
alert(VO === global); // true

In the example above we see that function foo is created in the global context and, accordingly, its__parent__ property is set to variable object of the global context, i.e. to the global object.

However, to get the activation object in SpiderMonkey with the same way is not possible: depending on the version, __parent__ for inner function returns either null or global object.

In Rhino, access to the activation object is allowed and available via the same way:

Example (Rhino):

var global = this;
var x = 10;
 
(function foo() {
 
  var y = 20;
 
  // the activation object of the "foo" context
  var AO = (function () {}).__parent__;
 
  print(AO.y); // 20
 
  // __parent__ of the current activation
  // object is already the global object,
  // i.e. the special chain of variable objects is formed,
  // so-called, a scope chain
  print(AO.__parent__ === global); // true
 
  print(AO.__parent__.x); // 10
 
})();

In this article we have moved further forward in studying of objects related with execution contexts. I hope the material is useful and has clarified some aspects and ambiguities which, probably, you had before. Further by the plan, the next chapters will be devoted to the Scope chainIdentifier resolution and, as consequence, Closures.

If you have questions, feel free to ask them in comments.

 

中文版链接地址 http://www.cnblogs.com/justinw/archive/2010/04/23/1718733.html

 

 

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

 

 

介绍

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

大部分ECMAScript程序员应该都知道变量与 执行上下文 密切相关:

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

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

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

下面我们具体来看一看,当我们声明数据时候的内部细节。

数据声明

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

变量对象 (缩写为VO)就是与执行上下文相关的对象(译者注:这个“对象”的意思就是指某个东西),它存储下列内容:
  • 变量 (var, VariableDeclaration);
  • 函数声明 (FunctionDeclaration, 缩写为FD);
  • 以及函数的形参

以上均在上下文中声明。

简单举例如下,一个变量对象完全有可能用正常的ECMAScript对象的形式来表现:

1
VO = {};

正如我们之前所说, VO就是执行上下文的属性(property):

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

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

当我们声明一个变量或一个函数的时候,同时还用变量的名称和值,在VO里创建了一个新的属性。

例如:

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

对应的变量对象是:

1
2
3
4
5
6
7
8
9
10
11
// Variable object of the global context
VO(globalContext) = {
  a: 10,
  test:
};
 
// Variable object of the "test" function context
VO(test functionContext) = {
  x: 30,
  b: 20
};

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

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

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

下面,我们详细展开探讨;

全局上下文中的变量对象

这里有必要先给全局对象(Global object)一个明确的定义:

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

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

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

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

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

因此,全局上下文中的变量对象就是全局对象自身(global object itself):

1
VO(globalContext) === global;

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

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

函数上下文中的变量对象

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

1
VO(functionContext) === AO;

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

1
2
3
4
<pre class="brush:javascript">AO = {
  arguments: <ArgO>
};
</pre>

Arguments objects 是函数上下文里的激活对象中的内部对象,它包括下列属性:

  • callee — 指向当前函数的引用;
  • length — 真正传递的参数的个数;
  • properties-indexes (字符串类型的整数) 属性的值就是函数的参数值(按参数列表从左到右排列)。 properties-indexes内部元素的个数等于arguments.length. properties-indexes 的值和实际传递进来的参数之间是共享的。(译者注:共享与不共享的区别可以对比理解为引用传递与值传递的区别)

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function foo(x, y, z) {
 
  alert(arguments.length); // 2 – quantity of passed arguments
  alert(arguments.callee === foo); // true
 
  alert(x === arguments[0]); // true
  alert(x); // 10
 
  arguments[0] = 20;
  alert(x); // 20
 
  x = 30;
  alert(arguments[0]); // 30
 
  // however, for not passed argument z,
  // related index-property of the arguments
  // object is not shared
 
  z = 40;
  alert(arguments[2]); // undefined
 
  arguments[2] = 50;
  alert(z); // 40
 
}
 
foo(10, 20);

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

分阶段处理上下文代码

现在我们终于触及到本文的核心内容。执行上下文的代码被分成两个基本的阶段来处理:

  • 进入执行上下文;
  • 执行代码;

变量对象的变化与这两个阶段紧密相关。

进入执行上下文

当进入执行上下文(代码执行之前)时,VO已被下列属性填充满(这些都已经在前文描述过):

  • 函数的所有形式参数(如果我们是在函数执行上下文中)
  • — 变量对象的一个属性,这个属性由一个形式参数的名称和值组成;如果没有对应传递实际参数,那么这个属性就由形式参数的名称和undefined值组成;

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

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

让我们看一个例子:

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

进入“test”函数的上下文时(传递参数10),AO如下:

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

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

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

执行代码

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

还是前面那个例子, AO/VO 在代码解释期间被修改如下:

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

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

另一个经典例子:

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

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

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

随后在执行代码阶段,VO做如下修改:

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

我们可以在第二、三个alert看到这个效果。

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

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

关于变量

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

任何时候,变量只能通过使用var关键字才能声明。

那么像下面这样分配:

1
a = 10;

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

让我们通过下面的实例看看具体的区别吧:

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

所有根源仍然是VO和它的修改阶段(进入上下文 阶段和执行代码 阶段):

进入上下文阶段:

1
2
3
VO = {
  a: undefined
};

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

让我们改变一下例子代码:

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

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

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

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

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

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

特殊实现: __parent__ 属性

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

例如 (SpiderMonkey, Rhino):

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

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

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

在Rhino中,用同样的方式访问激活对象是完全可以的。

例如 (Rhino):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var global = this;
var x = 10;
 
(function foo() {
 
  var y = 20;
 
  // the activation object of the "foo" context
  var AO = (function () {}).__parent__;
 
  print(AO.y); // 20
 
  // __parent__ of the current activation
  // object is already the global object,
  // i.e. the special chain of variable objects is formed,
  // so-called, a scope chain
  print(AO.__parent__ === global); // true
 
  print(AO.__parent__.x); // 10
 
})();

结论

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

如果您有问题,我很高兴在下面评论中解答。

其他参考

 

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics