我们在创建应用程序的时候,总免不了要声明变量和函数。那么,当我们需要使用这些东西的时候,解释器(interpreter)是怎么样、从哪里找到我们的数据(函数,变量)的,这个过程究竟发生了什么呢?
大部分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
同样,很多程序员也知道,基于当前版本的规范,独立作用域只能通过“函数(function)”代码类型的执行上下文创建。那么,想对于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
下面我们具体来看一看,当我们声明数据时候的内部细节。
数据声明如果变量与执行上下文相关,那么它自己应该知道它的数据存储在哪里和如何访问。这种机制被称作 变量对象(variable object).
变量对象 (缩写为VO)就是与执行上下文相关的对象(译者注:这个“对象”的意思就是指某个东西),它存储下列内容:
变量 (var, VariableDeclaration);
函数声明 (FunctionDeclaration, 缩写为FD);
以及函数的形参
以上均在上下文中声明。
简单举例如下,一个变量对象完全有可能用正常的ECMAScript对象的形式来表现:
VO = {};
正如我们之前所说, VO就是执行上下文的属性(property):
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: }; // 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属性就是引用全局对象自身的属性(当然,并不是所有的具体实现都是这样):
global = { Math: <...>, String: <...> ... ... window: global };
因为全局对象是不能通过名称直接访问的,所以当访问全局对象的属性时,通常忽略前缀。尽管如此,通过全局上下文的this还是有可能直接访问到全局对象的,同样也可以通过引用自身的属性来访问,例如,DOM中的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;
因此,全局上下文中的变量对象就是全局对象自身(global object itself):
VO(globalContext) === global;
准确理解“全局上下文中的变量对象就是全局对象自身”是非常必要的,基于这个事实,在全局上下文中声明一个变量时,我们才能够通过全局对象的属性间接访问到这个变量(例如,当事先未知变量名时):