咱们老是会在程序中定义一些函数和变量,以后会使用这些函数和变量来构建咱们的系统。
然而,对于解释器来讲,它又是如何以及从哪里找到这些数据的(函数,变量)?当引用一个对象的时候,在解释器内部又发生了什么? 咱们大多数人都知道,变量和执行上下文是密切相关的:程序员
var a = 10; // 全局上下文中的变量
(function () {
var b = 20; // 函数上下文中的局部变量
})();
alert(a); // 10
alert(b); // "b" is not defined
复制代码
不只如此,许多程序员也都知道,ECMAScript标准中指出独立的做用域只有经过“函数代码”(可执行代码类型中的一种)才能建立出来。比方说,与C/C++不一样的是,在ECMAScript中for循环的代码块是没法建立本地上下文的:浏览器
for (var k in {a: 1, b: 2}) {
alert(k);
}
alert(k); // 尽管循环已经结束,可是变量“k”仍然在做用域中
复制代码
下面就来详细介绍下,当声明变量和函数的时候,究竟发生了什么。bash
A variable object (in abbreviated form — VO) is a special object related with an execution context and which stores:ecmascript
VO={}
复制代码
VO同时也是有一个执行上下文的属性:
activeExecutionContext = {
VO: {
// 上下文中的数据 (变量声明(var), 函数声明(FD), 函数形参(function arguments))
}
};
复制代码
对变量的间接引用(经过VO的属性名)只容许发生在全局上下文中的变量对象上(全局对象自己就是变量对象,这部分会在后续做相应的介绍)。 对于其余的上下文而言,是没法直接引用VO的,由于VO是实现层的。
声明新的变量和函数的过程其实就是在VO中建立新的和变量以及函数名对应的属性和属性值的过程。
ide
以下所示: 函数
var a = 10;
function test(x) {
var b = 20;
};
test(30);
复制代码
上述代码对应的变量对象则以下所示:
// 全局上下文中的变量对象
VO(globalContext) = {
a: 10,
test:
};
// “test”函数上下文中的变量对象
VO(test functionContext) = {
x: 30,
b: 20
};
复制代码
可是,在实现层(标准中定义的),变量对象只是一个抽象的概念。在实际执行上下文中,VO可能彻底不叫VO,而且初始的结构也可能彻底不一样。
AbstractVO (generic behavior of the variable instantiation process)
║
╠══> GlobalContextVO
║ (VO === this === global)
║
╚══> FunctionContextVO
(VO === AO, object and are added)
复制代码
接下来对这块内容进行详细介绍
全局对象是一个在进入任何执行上下文前就建立出来的对象;此对象以单例形式存在;它的属性在程序任何地方均可以直接访问,其生命周期随着程序的结束而终止。工具
全局对象在建立的时候,诸如Math,String,Date,parseInt等等属性也会被初始化,同时,其中一些对象会指向全局对象自己--好比,DOM中,全局对象上的window属性就指向了全局对象(可是,并不是全部的实现都是如此): post
global = {
Math: <...>,
String: <...>
...
...
window: global
};</...></...>
复制代码
在引用全局对象的属性时,前缀一般能够省略,由于全局对象是不能经过名字直接访问的。然而,经过全局对象上的this值,以及经过如DOM中的window对象这样递归引用的方式均可以访问到全局对象:
String(10); // 等同于 global.String(10);
// 带前缀
window.a = 10; // === global.window.a = 10 === global.a = 10;
this.b = 20; // global.b = 20;
复制代码
回到全局上下文的变量对象上--这里变量对象是全局对象自己:
VO(globalContext) === glbal;
复制代码
准确地理解这个事实是很是必要的:正是因为这个缘由,当在全局上下文中声明一个变量时,能够经过全局对象上的属性来间接的引用该变量(比方说,当变量名提早未知的状况下)
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"
复制代码
活跃对象会在进入函数上下文的时候建立出来,初始化的时候会建立一个arguments属性, 其值就是Arguments对象:测试
AO = {
arguments:
}
复制代码
Arguments对象是活跃对象上的属性,它包含了以下属性:ui
以下所示:
function foo(x, y, z) {
// 定义的函数参数(x,y,z)的个数
alert(foo.length); // 3
// 实际传递的参数个数
alert(arguments.length); // 2
// 引用函数自身
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
// 然而,对于没有传递的参数z,
// 相关的arguments对象的index-property是不共享的
z = 40;
alert(arguments[2]); // undefined
arguments[2] = 50;
alert(z); // 40
}
foo(10, 20);
复制代码
上述例子,在当前的Google Chrome浏览器中有个bug——参数z和arguments[2]也是互相共享的。
对变量对象的修改和这两个阶段密切相关。
要注意的是,这两个处理阶段是通用的行为,与上下文类型无关(不论是全局上下文仍是函数上下文都是一致的)。
看下面这个🌰:
function test(a, b) {
var c = 10;
function d() {}
var e = function _e() {};
(function x() {});
}
test(10); // call
复制代码
当以10位参数进入"test"函数上下文的时候,对应的AO以下所示:
AO(test) = {
a: 10,
b: undefined,
c: undefined,
d:
e: undefined
};
复制代码
注意了,上面的AO并不包含函数"x"。这是由于这里的"x"并非函数声明而是函数表达式(FunctionExpression 简称FE), 函数表达式不会对VO形成影响。尽管函数"_e"也是函数表达式,然而,正如咱们看到的,因为它被赋值给了变量"e",所以它能够经过"e"来访问到。关于函数声明和函数表达式会在别的文章作具体介绍。 至此,处理上下文代码的第一阶段介绍完了,接下来介绍第二阶段--执行代码阶段。
AO['c'] = 10;
AO['e'] = <指向函数表达式"_e">;
复制代码
再次注意到,这里函数表达式"_e"仍在内存中,这是由于它被保存在声明的变量"e"中,而一样是函数表达式的"x"却不在AO/VO中:若是尝试在定义前或者定义后调用"x"函数,这时会发生"x未定义"的错误。未保存的函数表达式只有在定义或者递归时才能调用。
以下是更加典型的例子:
alert(x); // function
var x = 10;
alert(x); // 10
x = 20;
function x() {};
alert(x); // 20
复制代码
上述例子中,为什么“x”打印出来是函数呢?为什么在声明前就能够访问到?又为什么不是10或者20呢?缘由在于,根据规则——在进入上下文的时候,VO会被填充函数声明; 同一阶段,还有变量声明“x”,可是,正如此前提到的,变量声明是在函数声明和函数形参以后,而且,变量声明不会对已经存在的一样名字的函数声明和函数形参发生冲突, 所以,在进入上下文的阶段,VO填充为以下形式:
VO = {};
VO['x'] =
// 发现var x = 10;
// 若是函数“x”还未定义
// 则 "x" 为undefined, 可是,在咱们的例子中
// 变量声明并不会影响同名的函数值
VO['x'] =
复制代码
随后,在执行代码阶段,VO被修改成以下所示:
VO['x'] = 10;
VO['x'] = 20;
复制代码
以下例子再次看到在进入上下文阶段,变量存储在VO中(所以,尽管else的代码块永远都不会执行到,而“b”却仍然在VO中):
if (true) {
var a = 1;
} else {
var b = 2;
}
alert(a); // 1
alert(b); // undefined, but not "b is not defined"
复制代码
以下赋值语句:
a = 10;
复制代码
仅仅是在全局对象上建立了新的属性(而不是变量)。“不是变量”并不意味着它没法改变,它是ECMAScript中变量的概念(它以后能够变为全局对象的属性,由于VO(globalContext) === global,还记得吧?)
不一样点以下所示:
alert(a); // undefined
alert(b); // "b" is not defined
b = 10;
var a = 20;
复制代码
接下来仍是要谈到VO和在不一样阶段对VO的修改(进入上下文阶段和执行代码阶段): 进入上下文:
VO = {
a: undefined
};
复制代码
咱们看到,这个阶段并无任何“b”,由于它不是变量,“b”在执行代码阶段才出现。(可是,在咱们这个例子中也不会出现,由于在“b”出现前就发生了错误)
将上述代码稍做改动:
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 修改时建立
复制代码
这里关于变量还有很是重要的一点:与简单属性不一样的是,变量是不能删除的{DontDelete},这意味着要想经过delete操做符来删除一个变量是不可能的。
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”执行上下文中,是能够删除变量的:
eval('var a = 10;');
alert(window.a); // 10
alert(delete a); // true
alert(window.a); // undefined
复制代码
利用某些debug工具,在终端测试过这些例子的童鞋要注意了:其中Firebug也是使用了eval来执行终端的代码。所以,这个时候var也是能够删除的。
以下所示(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
复制代码
上述例子中,能够看到函数foo是在全局上下文中建立的,相应的,它的__parent__属性设置为全局上下文的变量对象,好比说:全局对象。
然而,在SpiderMonkey中以相同的方式获取活跃对象是不可能的:不一样的版本表现都不一样,内部函数的__parent__属性会返回null或者全局对象。 在Rhino中,以相同的方式获取活跃对象是容许的
以下所示(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
})();
复制代码
重学JavaScript深刻理解系列(一)
重学JavaScript深刻理解系列(三)
重学JavaScript深刻理解系列(四)
重学JavaScript深刻理解系列(五)
重学JavaScript深刻理解系列(六)