引见
JavaScript编程的时候总避免不了声明函数和变量,以胜利构建我们的体系,然则诠释器是怎样而且在什么地方去查找这些函数和变量呢?我们援用这些对象的时候终究发生了什么?
原始宣布:Dmitry A. Soshnikov 宣布时候:2009-06-27 俄文地点:http://dmitrysoshnikov.com/ecmascript/ru-chapter-2-variable-object/
英文翻译:Dmitry A. Soshnikov 宣布时候:2010-03-15 英文地点:http://dmitrysoshnikov.com/ecmascript/chapter-2-variable-object/ 部份难以翻译的句子参考了justinw的中文翻译
大多数ECMAScript递次员应当都晓得变量与实行高低文有密切关系:
var a = 10; // 全局高低文中的变量
(function () {
var b = 20; // function高低文中的部分变量
})();
console.log(a); // 10
console.log(b); // 全局变量 "b" 没有声明
而且,许多递次员也都晓得,当前 ECMAScript
范例指出自力作用域只能经由历程”函数(function
)”代码范例的实行高低文建立。也就是说,相关于C/C++
来讲,ECMAScript
里的 for
轮回并不能建立一个部分的高低文。
for (var k in {a: 1, b: 2}) {
console.log(k);
}
console.log(k); // 只管轮回已完毕但变量k依旧在当前作用域
我们来看看一下,我们声明数据的时候究竟都发现了什么细节。
数据声明
假如变量与实行高低文相干,那变量本身应当晓得它的数据存储在那里,而且晓得怎样接见。这类机制称为变量对象(variable object)。
变量对象
(缩写为VO)是一个与实行高低文相干的特别对象,它存储着在高低文中声明的以下内容:变量
(var, 变量声明);函数声明
(FunctionDeclaration, 缩写为FD);函数的形参
举例来讲,我们能够用平常的 ECMAScript
对象来示意一个变量对象:
VO = {};
就像我们所说的, VO
就是实行高低文的属性( property
):
activeExecutionContext = {
VO: {
// 高低文数据(var, FD, function arguments)
}
};
只要全局高低文的变量对象许可经由历程 VO
的属性称号来间接接见(由于在全局高低文里,全局对象本身就是变量对象,稍后会细致引见),在别的高低文中是不能直接接见 VO
对象的,由于它只是内部机制的一个完成。
当我们声明一个变量或一个函数的时候,和我们建立 VO
新属性的时候一样没有别的区分(即:有称号以及对应的值)。
比方:
var a = 10;
function test(x) {
var b = 20;
};
test(30);
对应的变量对象是:
// 全局高低文的变量对象
VO(globalContext) = {
a: 10,
test: <reference to function>
};
// test函数高低文的变量对象
VO(test functionContext) = {
x: 30,
b: 20
};
在详细完成层面(以及范例中)变量对象只是一个笼统观点。(从实质上说,在详细实行高低文中,VO
称号是不一样的,而且初始构造也不一样。
差别实行高低文中的变量对象
关于一切范例的实行高低文来讲,变量对象的一些操纵(如变量初始化)和行动都是共通的。从这个角度来看,把变量对象作为笼统的基础事物来明白更加轻易。一样在函数高低文中也定义和变量对象相干的分外内容。
笼统变量对象VO (变量初始化历程的平常行动) ║ ╠══> 全局高低文变量对象GlobalContextVO ║ (VO === this === global) ║ ╚══> 函数高低文变量对象FunctionContextVO (VO === AO, 而且添加了<arguments>和<formal parameters>)
我们来细致看一下:
全局高低文中的变量对象
起首,我们要给全局对象一个明白的定义:
全局对象(
Global object
) 是在进入任何实行高低文之前就已建立了的对象; 这个对象只存在一份,它的属性在递次中任何地方都能够接见,全局对象的生命周期终止于递次退出那一刻。
全局对象初始建立阶段将Math、String、Date、parseInt作为本身属性,等属性初始化,一样也能够有分外建立的别的对象作为属性(其能够指向到全局对象本身)。比方,在DOM中,全局对象的window属性就能够援用全局对象本身(固然,并非一切的详细完成都是如许):
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) === global;
异常有必要要明白上述结论,基于这个道理,在全局高低文中声明的对应,我们才够间接经由历程全局对象的属性来接见它(比方,事前不晓得变量称号)。
var a = new String('test');
console.log(a); // 直接接见,在VO(globalContext)里找到:"test"
console.log(window['a']); // 间接经由历程global接见:global === VO(globalContext): "test"
console.log(a === this.a); // true
var aKey = 'a';
console.log(window[aKey]); // 间接经由历程动态属性称号接见:"test"
函数高低文中的变量对象
在函数实行高低文中,VO是不能直接接见的,此时由运动对象(activation object,缩写为AO)饰演VO的角色。
VO(functionContext) === AO;
运动对象是在进入函数高低文时候被建立的,它经由历程函数的arguments属性初始化。arguments属性的值是Arguments对象:
AO = {
arguments: <ArgO>
};
Arguments对象是运动对象的一个属性,它包括以下属性:
callee — 指向当前函数的援用
length — 真正通报的参数个数
properties-indexes (字符串范例的整数) 属性的值就是函数的参数值(按参数列表从左到右分列)。 properties-indexes内部元素的个数即是arguments.length. properties-indexes 的值和现实通报进来的参数之间是同享的。
比方:
function foo(x, y, z) {
// 声明的函数参数数目arguments (x, y, z)
console.log(foo.length); // 3
// 真正传进来的参数个数(only x, y)
console.log(arguments.length); // 2
// 参数的callee是函数本身
console.log(arguments.callee === foo); // true
// 参数同享
console.log(x === arguments[0]); // true
console.log(x); // 10
arguments[0] = 20;
console.log(x); // 20
x = 30;
console.log(arguments[0]); // 30
// 不过,没有传进来的参数z,和参数的第3个索引值是不同享的
z = 40;
console.log(arguments[2]); // undefined
arguments[2] = 50;
console.log(z); // 40
}
foo(10, 20);
这个例子的代码,在当前版本的Google Chrome浏览器里有一个bug — 纵然没有通报参数z,z和arguments[2]依然是同享的。
处置惩罚高低文代码的2个阶段
如今我们终究到了本文的中心点了。实行高低文的代码被分红两个基础的阶段来处置惩罚:
进入实行高低文
实行代码
变量对象的修正变化与这两个阶段严密相干。
注:这2个阶段的处置惩罚是平常行动,和高低文的范例无关(也就是说,在全局高低文和函数高低文中的表现是一样的)。
进入实行高低文
当进入实行高低文(代码实行之前)时,VO里已包括了以下属性(前面已说了): 函数的一切形参(假如我们是在函数实行高低文中) — 由称号和对应值构成的一个变量对象的属性被建立;没有通报对应参数的话,那末由称号和undefined值构成的一种变量对象的属性也将被建立。 一切函数声明(FunctionDeclaration, FD) –由称号和对应值(函数对象(function-object))构成一个变量对象的属性被建立;假如变量对象已存在雷同称号的属性,则完整替代这个属性。 一切变量声明(var, VariableDeclaration) — 由称号和对应值(undefined)构成一个变量对象的属性被建立;假如变量称号跟已声明的形式参数或函数雷同,则变量声明不会滋扰已存在的这类属性。
让我们看一个例子:
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: <reference to FunctionDeclaration "d">
e: undefined
};
注重,AO里并不包括函数”x”。这是由于”x” 是一个函数表达式(FunctionExpression, 缩写为 FE) 而不是函数声明,函数表达式不会影响VO。 不论怎样,函数”_e” 一样也是函数表达式,然则就像我们下面将看到的那样,由于它分配给了变量 “e”,所以它能够经由历程称号”e”来接见。 函数声明FunctionDeclaration与函数表达式FunctionExpression 的差别,将在第15章Functions举行细致的讨论,也能够参考本系列第2章揭秘定名函数表达式来相识。
这以后,将进入处置惩罚高低文代码的第二个阶段 — 实行代码。
代码实行
这个周期内,AO/VO已具有了属性(不过,并非一切的属性都有值,大部份属性的值照样体系默许的初始值undefined )。
照样前面谁人例子, AO/VO在代码诠释时期被修正以下:
AO['c'] = 10;
AO['e'] = <reference to FunctionExpression "_e">;
再次注重,由于FunctionExpression”_e”保留到了已声明的变量”e”上,所以它依然存在于内存中。而FunctionExpression “x”却不存在于AO/VO中,也就是说假如我们想尝试挪用”x”函数,不论在函数定义之前照样以后,都邑涌现一个毛病”x is not defined”,未保留的函数表达式只要在它本身的定义或递归中才被挪用。
另一个典范例子:
console.log(x); // function
var x = 10;
console.log(x); // 10
x = 20;
function x() {};
console.log(x); // 20
为何第一个console.log “x” 的返回值是function,而且它照样在”x” 声明之前接见的”x” 的?为何不是10或20呢?由于,依据范例函数声明是在当进入高低文时填入的; 赞同周期,在进入高低文的时候另有一个变量声明”x”,那末正如我们在上一个阶段所说,变量声明在递次上跟在函数声明和形式参数声明以后,而且在这个进入高低文阶段,变量声明不会滋扰VO中已存在的同名函数声明或形式参数声明,因而,在进入高低文时,VO的构造以下:
VO = {};
VO['x'] = <reference to FunctionDeclaration "x">
// 找到var x = 10;
// 假如function "x"没有已声明的话
// 这时候"x"的值应当是undefined
// 然则这个case里变量声明没有影响同名的function的值
VO['x'] = <the value is not disturbed, still function>
紧接着,在实行代码阶段,VO做以下修正:
VO['x'] = 10;
VO['x'] = 20;
我们能够在第二、三个console.log看到这个结果。
鄙人面的例子里我们能够再次看到,变量是在进入高低文阶段放入VO中的。(由于,虽然else部份代码永久不会实行,然则不论怎样,变量”b”依然存在于VO中。)
if (true) {
var a = 1;
} else {
var b = 2;
}
console.log(a); // 1
console.log(b); // undefined,不是b没有声明,而是b的值是undefined
关于变量
一般,各种文章和JavaScript相干的书本都宣称:”不论是运用var关键字(在全局高低文)照样不运用var关键字(在任何地方),都能够声明一个变量”。请记着,这是 毛病 的观点: 任何时候,变量只能经由历程运用var关键字才声明。
上面的赋值语句:
a = 10;
这仅仅是给全局对象建立了一个新属性(但它不是变量)。”不是变量”并非说它不能被转变,而是指它不相符ECMAScript范例中的变量观点,所以它”不是变量”(它之所以能成为全局对象的属性,完整是由于VO(globalContext) === global,人人还记得这个吧?)。
让我们经由历程下面的实例看看详细的区分吧:
console.log(a); // undefined
console.log(b); // "b" 没有声明
b = 10;
var a = 20;
一切泉源依然是VO和进入高低文阶段和代码实行阶段:
进入高低文阶段:
VO = {
a: undefined
};
我们能够看到,由于”b”不是一个变量,所以在这个阶段根本就没有”b”,”b”将只在代码实行阶段才会涌现(然则在我们这个例子里,还没有到那就已出错了)。
让我们转变一下例子代码:
console.log(a); // undefined, 这个人人都晓得,
b = 10;
console.log(b); // 10, 代码实行阶段建立
var a = 20;
console.log(a); // 20, 代码实行阶段修正
关于变量,另有一个主要的学问点。变量相关于简朴属性来讲,变量有一个特征(attribute):{DontDelete},这个特征的寄义就是不能用delete操纵符直接删除变量属性。
a = 10;
console.log(window.a); // 10
console.log(delete a); // true
console.log(window.a); // undefined
var b = 20;
console.log(window.b); // 20
console.log(delete b); // false
console.log(window.b); // still 20
然则这个划定规矩在有个高低文里不起走样,那就是eval高低文,变量没有{DontDelete}特征。
eval('var a = 10;');
console.log(window.a); // 10
console.log(delete a); // true
console.log(window.a); // undefined
运用一些调试东西(比方:Firebug)的控制台测试该实例时,请注重,Firebug一样是运用eval来实行控制台里你的代码。因而,变量属性一样没有{<span style=”color: #ff6600;”>DontDelete</span>}特征,能够被删除。
特别完成: parent 属性
前面已提到过,按标准范例,运动对象是不可能被直接接见到的。然则,一些详细完成并没有完整恪守这个划定,比方SpiderMonkey和Rhino;的完成中,函数有一个特别的属性 parent,经由历程这个属性能够直接援用到运动对象(或全局变量对象),在此对象里建立了函数。
比方 (SpiderMonkey, Rhino):
var global = this;
var a = 10;
function foo() {}
console.log(foo.__parent__); // global
var VO = foo.__parent__;
console.log(VO.a); // 10
console.log(VO === global); // true
在上面的例子中我们能够看到,函数foo是在全局高低文中建立的,所以属性parent 指向全局高低文的变量对象,即全局对象。
但是,在SpiderMonkey顶用一样的体式格局接见运动对象是不可能的:在差别版本的SpiderMonkey中,内部函数的parent 偶然指向null ,偶然指向全局对象。
在Rhino中,用一样的体式格局接见运动对象是完整能够的。
比方 (Rhino):
var global = this;
var x = 10;
(function foo() {
var y = 20;
// "foo"高低文里的运动对象
var AO = (function () {}).__parent__;
print(AO.y); // 20
// 当前运动对象的__parent__ 是已存在的全局对象
// 变量对象的特别链形成了
// 所以我们叫做作用域链
print(AO.__parent__ === global); // true
print(AO.__parent__.x); // 10
})();
总结
在这篇文章里,我们深切进修了跟实行高低文相干的对象。我愿望这些学问对您来讲能有所协助,能处理一些您曾碰到的题目或疑心。根据设计,在后续的章节中,我们将讨论作用域链,标识符剖析,闭包。
有任何题目,我很愉快鄙人面批评中能帮你解答。
别的参考
10.1.3 – Variable Instantiation;
10.1.5 – Global Object;
10.1.6 – Activation Object;
10.1.8 – Arguments Object.
关于本文
本文转自TOM大叔的深切明白JavaScript系列。声明一下,本人一切整顿的文章均不是照搬全抄,到场本身的明白和细致的注解,以及修正了一些语病错字等。
【深切明白JavaScript系列】文章,包括了原创,翻译,转载,整顿等各范例文章,原文是TOM大叔的一个异常不错的专题,现将其重新整顿宣布。感谢大叔。假如你以为本文不错,请帮助点个引荐,支撑一把,感激涕零。
更多优异文章迎接关注我的专栏