作者的话
有很多文章已经对ECMAScript的核心概念做了详尽解读。本系列文章翻译自Dmitry Soshnikov的个人网站,相信不少人已经看过原文或者译文。原文简洁易懂并且严谨,条理清晰地阐明了所有JavaScript开发者不得不深入理解的ECMAScript核心概念。重复翻译的原因主要是为了个人收藏、整理之用。初次翻译,技巧拙劣,如有不足,请不吝赐教。
正文
介绍
我们总是在程序中声明函数和变量,然后成功地用作构建我们的系统。但是,解释器怎样以及在哪里找到我们的数据(函数和变量)?当我们引用需要的对象的时候发生了什么?
许多ECMAScript开发者知道变量和执行上下文紧密相关。
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
同时,许多程序员也知道当前版本(ES6之前,译者注)的标准的独立作用域是通过“函数”代码类型的执行上下文创建的。比如,和C/C++相反,ECMAScript中的for
循环块不会创建本地上下文。
for (var k in {a: 1, b: 2}) {
alert(k);
}
alert(k); // variable "k" still in scope even the loop is finished
让我们看看当我们声明数据时的所发生的细节。
数据声明
如果变量和执行上下文相关,那么应该知道变量的数据存在哪里以及如何获取它们。这个机制被称为变量对象
变量对象
(简称:VO
)是一个与执行上下文相关的特殊对象,它存储了在上下文中声明的:
- 变量
- 函数声明
- 函数形参
注意,在ES5中,变量对象
的概念已经被词法环境
模式取代,可以在适当的章节找到它的详细描述。
示意性的举个例子,可以将变量对象表示成一个普通的ECMAScript对象:
VO = {};
正如我们所说,VO
是执行上下文的属性:
activeExecutionContext = {
VO: {
// context data (var, FD, function arguments)
}
};
只允许在全局上下文
(全局对象就是变量对象的地方)中,对变量间接引用(通过VO
的属性名)。对于其他上下文,对VO
对象的直接引用是不可能的,这纯粹是实现机制。
当我们声明了一个变量或函数,除了使用我们的变量的名字和值创建VO
的新属性,没别的了。
比如:
var a = 10;
function test(x) {
var b = 20;
};
test(30);
相应的变量对象是这样:
// 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
};
但是在实现层面(以及规范)中,变量对象是一个抽象的概念。物理上,在具体的执行上下文中,VO
被叫做不同的名称,有不同的初始结构。
不同执行上下文中的变量对象
对于所有的执行上下文类型,变量对象的一些操作(比如变量实例化)和行为是共通的。从这点看,将变量对象看作是抽象的基础的东西很便利。函数上下文
也可以定义与变量对象相关的额外内容。
AbstractVO (generic behavior of the variable instantiation process)
║
╠══> GlobalContextVO
║ (VO === this === global)
║
╚══> FunctionContextVO
(VO === AO, <arguments> object and <formal parameters> are added)
我们详细解读一下。
全局上下文的变量对象
现在,应该先给出全局对象
的定义。
全局对象
是在进入任何执行上下文前创建的对象;这个对象仅存在一份,它的属性可以在程序的任何位置访问到,
全局对象
的生命周期伴随着程序结束而结束。
在创建时,全局对象使用诸如:Math、String、Date、parseInt等初始化,也可以使用附加对象初始化,附加对象可以是全局对象本身的引用–比如在BOM中,全局对象的window
属性引用全局对象(不是在所有的实现中都是如此)。
global = {
Math: <...>,
String: <...>
...
...
window: global
};
当引用全局对象的属性,前缀往往可以省略,因为全局对象不能通过名字直接访问到。然而,在全局上下文
中可以通过this
值访问到它,也可以通过对它的递归引用访问到它,比如BOM中的window
,因此可以简写:
String(10); // means global.String(10);
// with prefixes
window.a = 10; // === global.window.a = 10 === global.a = 10;
this.b = 20; // global.b = 20;
所以回到全局上下文
的变量对象,这里变量对象就是全局对象
本身:
VO(globalContext) === global;
很有必要理解这个事实,由于这个原因,在全局上下文
中声明一个变量,我们可以通过全局对象
的属性间接访问它(比如,当变量名事先未知时):
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"