來源:仗劍走天涯!
關(guān)于javascript的作用域的一些總結(jié),主要參考以上文章,加上自己的整理的理解。
近日對(duì)javascript的作用域,以及它的運(yùn)行過程,感覺不甚了解,尤其在使用閉包的時(shí)候,感覺都是模糊不清的。
與是在網(wǎng)上一探究竟,可是沒想,網(wǎng)上的大多文章都是含糊其次,且大多雷同,經(jīng)多方查找,加上自己看一些書。
最終對(duì)javascript有一絲絲了解。記錄下來,以防忘記。
要徹底了解javascript的作用域就要了解一些概念。
1 運(yùn)行期上下文
在javascript中,只有函數(shù)能夠創(chuàng)建出獨(dú)立的作用域,需要注意的是for循環(huán)是不能創(chuàng)建的,否則你的代碼就可能得到意想不到的結(jié)果。
4for(var k in {a:1, b:2}) {
alert(k);
}
alert(k);
即使循環(huán)結(jié)束,一樣可以alert出k的值.
2變量對(duì)象以及活動(dòng)對(duì)象(VO/AO)
變量對(duì)象(縮寫為VO)是一個(gè)與執(zhí)行上下文相關(guān)的特殊對(duì)象,它存儲(chǔ)著在上下文中聲明的以下內(nèi)容:
變量 (var,變量聲明);
函數(shù)聲明 (FunctionDeclaration,縮寫為FD);
函數(shù)的形參
只有全局上下文的變量對(duì)象允許通過VO的屬性名稱來間接訪問(因?yàn)樵谌稚舷挛睦?,全局?duì)象自身就是變量對(duì)象),在其它上下文中是不能直接訪問VO對(duì)象的,因?yàn)樗皇莾?nèi)部機(jī)制的一個(gè)實(shí)現(xiàn),通常使用AO(activation object來保存變量)。
VO保存變量對(duì)象示例:
6var a =10;
functiontest(x) {
var b =20;
};
test(30);
對(duì)應(yīng)的變量對(duì)象是:
// 全局上下文的變量對(duì)象
9VO(globalContext) = {
a:10,
test:
};
// test函數(shù)上下文的變量對(duì)象
VO(test functionContext) = {
x:30,
b:20
};
2.1全局上下文中的變量對(duì)象(VO)
首先,我們要給全局對(duì)象一個(gè)明確的定義:
全局對(duì)象(Global object) 是在進(jìn)入任何執(zhí)行上下文之前就已經(jīng)創(chuàng)建了的對(duì)象;
這個(gè)對(duì)象只存在一份,它的屬性在程序中任何地方都可以訪問,全局對(duì)象的生命周期終止于程序退出那一刻。
全局對(duì)象初始創(chuàng)建階段將Math、String、Date、parseInt作為自身屬性,等屬性初始化,同樣也可以有額外創(chuàng)建的其它對(duì)象作為屬性(其可以指向到全局對(duì)象自身)。例如,在DOM中,全局對(duì)象的window屬性就可以引用全局對(duì)象自身(當(dāng)然,并不是所有的具體實(shí)現(xiàn)都是這樣):
7global = {
Math: <...>,
String:<...>
window: global//引用自身
};
當(dāng)訪問全局對(duì)象的屬性時(shí)通常會(huì)忽略掉前綴,這是因?yàn)槿謱?duì)象是不能通過名稱直接訪問的。不過我們依然可以通過全局上下文的this來訪問全局對(duì)象,同樣也可以遞歸引用自身。例如,DOM中的window。綜上所述,代碼可以簡(jiǎn)寫為:
16String(10);// 就是global.String(10);
// 帶有前綴
window.a =10;// === global.window.a = 10 ===global.a = 10;
this.b =20;//global.b = 20;
alert(this=== window);
這里可以看到,用winodow調(diào)用其實(shí)是用的VO中的一個(gè)屬性,而用this,則是用的全局變量。
因此,回到全局上下文中的變量對(duì)象——在這里,變量對(duì)象就是全局對(duì)象自己:
VO(globalContext) === global;
非常有必要要理解上述結(jié)論,基于這個(gè)原理,在全局上下文中聲明的對(duì)應(yīng),我們才可以間接通過全局對(duì)象的屬性來訪問它(例如,事先不知道變量名稱)。
var a =newString('test');
alert(a);// 直接訪問,在VO(globalContext)里找到:test
alert(window['a']);// 間接通過global訪問:global === VO(globalContext): test
alert(a ===this.a);// true
var aKey ='a';
alert(window[aKey]);// 間接通過動(dòng)態(tài)屬性名稱訪問:test
2.2函數(shù)上下文中的變量對(duì)象(AO)
在函數(shù)執(zhí)行上下文中,VO是不能直接訪問的,此時(shí)由活動(dòng)對(duì)象(activation object,縮寫為AO)扮演VO的角色。
VO(functionContext)=== AO;
活動(dòng)對(duì)象是在進(jìn)入函數(shù)上下文時(shí)刻被創(chuàng)建的,它通過函數(shù)的arguments屬性初始化。arguments屬性的值是Arguments對(duì)象:
3AO = {
arguments:
};
Arguments對(duì)象是活動(dòng)對(duì)象的一個(gè)屬性,它包括如下屬性:
callee — 指向當(dāng)前函數(shù)的引用
length — 真正傳遞的參數(shù)個(gè)數(shù)
properties-indexes (字符串類型的整數(shù)) 屬性的值就是函數(shù)的參數(shù)值(按參數(shù)列表從左到右排列)。 properties-indexes內(nèi)部元素的個(gè)數(shù)等于arguments.length. properties-indexes 的值和實(shí)際傳遞進(jìn)來的參數(shù)之間是共享的。
例如:
21function foo(x, y, z) {
// 聲明的函數(shù)參數(shù)數(shù)量arguments (x, y, z)
alert(foo.length);// 3
// 真正傳進(jìn)來的參數(shù)個(gè)數(shù)(only x, y)
alert(arguments.length);// 2
// 參數(shù)的callee是函數(shù)自身
alert(arguments.callee=== foo);// true
// 參數(shù)共享
alert(x === arguments[0]);// true
alert(x);//10
arguments[0] =20;
alert(x);//20
x =30;
alert(arguments[0]);// 30
// 不過,沒有傳進(jìn)來的參數(shù)z,和參數(shù)的第3個(gè)索引值是不共享的
z =40;
alert(arguments[2]);// undefined
arguments[2] =50;
alert(z);// 40
}
foo(10,20);
這個(gè)例子的代碼,在當(dāng)前版本的Google Chrome瀏覽器里有一個(gè)bug — 即使沒有傳遞參數(shù)z,z和arguments[2]仍然是共享的。
3 作用域鏈
從上面可以知道,每個(gè)上下文擁有自己的變量對(duì)象:對(duì)于全局上下文,它是全局對(duì)象自身;對(duì)于函數(shù),它是活動(dòng)對(duì)象。
9var x =10;
function foo() {
var y =20;
function bar(){
alert(x +y);
}
returnbar;
}
foo()();// 30
作用域鏈正是內(nèi)部上下文所有變量對(duì)象(包括父變量對(duì)象)的列表。此鏈用來變量查詢。即在上面的例子中,“bar”上下文的作用域鏈包括AO(bar)、AO(foo)和VO(global)。這里的AO(foo)和VO(global)都是來自父變量對(duì)象.
1
作用域鏈與一個(gè)執(zhí)行上下文相關(guān),變量對(duì)象的鏈用于在標(biāo)識(shí)符解析中變量查找。
函數(shù)上下文的作用域鏈在函數(shù)調(diào)用時(shí)創(chuàng)建的,包含活動(dòng)對(duì)象和這個(gè)函數(shù)內(nèi)部的[[scope]]屬性。
9在上下文中示意如下:
activeExecutionContext= {
VO: {...},//or AO? 上下文初始化的VO/AO
this: thisValue,
Scope: [// Scope chain,作用域鏈
// 所有變量對(duì)象的列表
// for identifiers lookup
]
};
其scope定義如下:
Scope= AO + [[Scope]]
這種聯(lián)合和標(biāo)識(shí)符解析過程,我們將在下面討論,這與函數(shù)的生命周期相關(guān)。
對(duì)于Scope = AO + [[Scope]],在函數(shù)被調(diào)用的時(shí)候,上下文會(huì)創(chuàng)建一個(gè)Scope China,也就是Scope屬性,然后初始化為函數(shù)的內(nèi)部屬性,也就是函數(shù)內(nèi)部屬性[[Scope]],再將進(jìn)入上下文時(shí)創(chuàng)建的VO/AO,壓入Scopechina的最前端。
3.1函數(shù)創(chuàng)建
函數(shù)的[[scope]]屬性是所有父變量對(duì)象的層級(jí)鏈,處于當(dāng)前函數(shù)上下文之上,在函數(shù)創(chuàng)建時(shí)(函數(shù)生命周期分為函數(shù)創(chuàng)建和函數(shù)調(diào)用階段)存于其中。函數(shù)能訪問更高一層上下文的變量對(duì)象,這種機(jī)制是通過函數(shù)內(nèi)部的[[scope]]屬性來實(shí)現(xiàn)的。
注意重要的一點(diǎn)--[[scope]]在函數(shù)創(chuàng)建時(shí)被存儲(chǔ)--靜態(tài)(不變的),永遠(yuǎn)永遠(yuǎn),直至函數(shù)銷毀。即:函數(shù)可以永不調(diào)用,但[[scope]]屬性已經(jīng)寫入,并存儲(chǔ)在函數(shù)對(duì)象中。由于是靜態(tài)存儲(chǔ),再配合上內(nèi)部函數(shù)的[[scope]]屬性是所有父變量的層級(jí)鏈,就導(dǎo)致了閉包的存在。如下:
9var x =10;
function foo() {
alert(x);
}
(function () {
var x =20;
foo();// 10,but not 20,這里會(huì)訪問foo中的[[scope]]的VO中的x
})();
這個(gè)例子也清晰的表明,一個(gè)函數(shù)(這個(gè)例子中為從函數(shù)“foo”返回的匿名函數(shù))的[[scope]]持續(xù)存在,即使是在函數(shù)創(chuàng)建的作用域已經(jīng)完成之后。
另外一個(gè)需要考慮的是--與作用域鏈對(duì)比,[[scope]]是函數(shù)的一個(gè)屬性而不是上下文??紤]到上面的例子,函數(shù)“foo”的[[scope]]如下:
foo.[[Scope]] = [ globalContext.VO// === Global ];
舉例來說,我們用通常的ECMAScript 數(shù)組展現(xiàn)作用域和[[scope]]。
3.2函數(shù)激活
正如在定義中說到的,進(jìn)入上下文創(chuàng)建AO/VO之后,上下文的Scope屬性(變量查找的一個(gè)作用域鏈)作如下定義:
Scope= AO|VO + [[Scope]]
上面代碼的意思是:活動(dòng)對(duì)象是作用域數(shù)組的第一個(gè)對(duì)象,即添加到作用域的前端。
Scope= [AO].concat([[Scope]]);
這個(gè)特點(diǎn)對(duì)于標(biāo)示符解析的處理來說很重要。
標(biāo)示符解析是一個(gè)處理過程,用來確定一個(gè)變量(或函數(shù)聲明)屬于哪個(gè)變量對(duì)象。
這個(gè)算法的返回值中,我們總有一個(gè)引用類型,它的base組件是相應(yīng)的變量對(duì)象(或若未找到則為null),屬性名組件是向上查找的標(biāo)示符的名稱。
標(biāo)識(shí)符解析過程包含與變量名對(duì)應(yīng)屬性的查找,即作用域中變量對(duì)象的連續(xù)查找,從最深的上下文開始,繞過作用域鏈直到最上層。
這樣一來,在向上查找中,一個(gè)上下文中的局部變量較之于父作用域的變量擁有較高的優(yōu)先級(jí)。萬一兩個(gè)變量有相同的名稱但來自不同的作用域,那么第一個(gè)被發(fā)現(xiàn)的是在最深作用域中。
我們用一個(gè)稍微復(fù)雜的例子描述上面講到的這些。
10var x =10;
functionfoo() {
var y =20;
function bar() {
var z =30;
alert(x +? y +z);
}
bar();
}
foo();//60
對(duì)此,我們有如下的變量/活動(dòng)對(duì)象,函數(shù)的的[[scope]]屬性以及上下文的作用域鏈:
全局上下文的變量對(duì)象是:
4globalContext.VO=== Global = {
x:10
foo:
};
在“foo”創(chuàng)建時(shí),“foo”的[[scope]]屬性是:
3foo.[[Scope]]= [
globalContext.VO
];
在“foo”激活時(shí)(進(jìn)入上下文),“foo”上下文的活動(dòng)對(duì)象是:
4fooContext.AO= {
y:20,
bar:
};
“foo”上下文的作用域鏈為:
6fooContext.Scope= fooContext.AO + foo.[[Scope]]// i.e.:
fooContext.Scope= [
fooContext.AO,
globalContext.VO
];
內(nèi)部函數(shù)“bar”創(chuàng)建時(shí),其[[scope]]為:
4bar.[[Scope]]= [
fooContext.AO,
globalContext.VO
];
在“bar”激活時(shí),“bar”上下文的活動(dòng)對(duì)象為:
3barContext.AO= {
z:30
};
“bar”上下文的作用域鏈為:
7barContext.Scope= barContext.AO + bar.[[Scope]]// i.e.:
barContext.Scope= [
barContext.AO,
fooContext.AO,
globalContext.VO
];
對(duì)“x”、“y”、“z”的標(biāo)識(shí)符解析如下:
11-x
--barContext.AO// not found
--fooContext.AO// not found
--globalContext.VO// found - 10
-y
--barContext.AO// not found
--fooContext.AO// found - 20
-z
--barContext.AO// found – 30
4閉包
創(chuàng)建閉包的常見方式是在一個(gè)函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù),以create ()函數(shù)為例:
10function create(){
var x =0
returnfunction(){
alert(++x);
};
}
var c = create();
c();//alert 1not 0
c();//alert 2not 0
c();//alert 3not 0
上面代碼中的內(nèi)部函數(shù)(一個(gè)匿名函數(shù)),訪問了外部函數(shù)中的變量x。即使這個(gè)內(nèi)部函數(shù)被返回了,且在其他地方被調(diào)用了,但它仍可訪問變量x.之所以還能夠訪問這個(gè)變量,是因?yàn)閮?nèi)部函數(shù)的作用域鏈中包含create ()的作用域。
當(dāng)某個(gè)函數(shù)第一次被調(diào)用時(shí),會(huì)創(chuàng)建一個(gè)執(zhí)行環(huán)境及相應(yīng)的作用域鏈,并把一個(gè)特殊的內(nèi)部屬性(即[[Scope]] ) 賦值給作用域鏈。然后使用this、arguments和其他命名參數(shù)的值來初始化函數(shù)的活動(dòng)對(duì)象。然后把該活動(dòng)對(duì)象壓入作用域鏈的最前端。所以在作用域鏈中,父級(jí)活動(dòng)對(duì)象始終處于第二位,直到作用域鏈終點(diǎn)的全局執(zhí)行環(huán)境。
在函數(shù)執(zhí)行過程中,為讀取和寫入變量的值,需要在作用域中查找變量:
9function compare(value1,value2){
if(value2value2){
return1;
}
else{
return0;
}
}
var result =compare(5,10);
上面的代碼先定義了compare()函數(shù),又在全局作用域調(diào)用了它。第一次調(diào)用compare()時(shí),會(huì)創(chuàng)建一個(gè)包含this、arguments、value1、value2的活動(dòng)對(duì)象。全局執(zhí)行環(huán)境的變量對(duì)象(包含this,result,compare)在compare()執(zhí)行環(huán)境的作用域中處于第二位,如圖:

此后又有一個(gè)活動(dòng)對(duì)象(在此作為變量對(duì)象使用)被創(chuàng)建并被推入執(zhí)行環(huán)境作用域鏈的前端。對(duì)于這個(gè)例子中compare()函數(shù)的執(zhí)行環(huán)境而言,其作用域鏈中包含兩個(gè)變量對(duì)象:本地活動(dòng)對(duì)象和全局變量對(duì)象。
作用域鏈本質(zhì)上是一個(gè)指向變量對(duì)象的指針列表,它只引用但不實(shí)際包含變量對(duì)象
一般來講在當(dāng)函數(shù)執(zhí)行完畢后,局部活動(dòng)對(duì)象就會(huì)被銷毀,內(nèi)在中僅保存全局作用域,但閉包情況有所不同。另一個(gè)函數(shù)內(nèi)部定義的函數(shù)會(huì)將包含函數(shù)的活動(dòng)對(duì)象添加到它的作用域鏈中。因此,createComparisonFunction()函數(shù)內(nèi)定義的匿名函數(shù)的作用域鏈中,實(shí)際上將會(huì)包含外部函數(shù)createComparisonFunction()的活動(dòng)對(duì)象,
9function createComparisonFunction(propertyName){
returnfunction(object1,object2){
varvalue1 = object1[propertyName];
varvalue1 = object2[propertyName];
if(value1 < value2){return-1;}
elseif (value1>value2){return1;}
else{return0;}
};
}
2var compare =createComparisonFunction(name);
var result =compare({name:'Nicholas},{name:Greg});

5閉包和變量
看如下代碼:
10function fn(){
var result =newArray();
for(vari=0;i<10;i++){
result[i]= function(){returni;}
}
returnresult;
}
var funcs = fn();
for(var i=0;i);
}
這段代碼代碼可能說是堪稱經(jīng)典。你也許知道結(jié)果會(huì)輸出10個(gè)10。
這里的原因還是因?yàn)樽饔糜虻膯栴}。
在fn 被調(diào)用結(jié)束后,那么在fn的作用鏈的AO中保存i的值為10。
主要的問題就是result[i]的方法的作用鏈中保存了父級(jí)活動(dòng)對(duì)象。那么當(dāng)激活result方法時(shí),會(huì)先去自已的活動(dòng)對(duì)象中搜索,當(dāng)沒有發(fā)現(xiàn)表示符時(shí),則會(huì)去父級(jí)活動(dòng)對(duì)象中搜索,當(dāng)搜索到i的時(shí)候,這個(gè)i已經(jīng)是10了。這就是為什么結(jié)果都為10。當(dāng)然需要如果需要得到你想要的結(jié)果,那么可以采用如下的方式。
5result[i] = function(num){
returnfunction(){
returnnum;
}
}(i);
這里為什么能得出正確的結(jié)果呢?其實(shí)這里是將修改了result的活動(dòng)對(duì)象。
第一個(gè)result的函數(shù)的活動(dòng)對(duì)象中多了一個(gè) num屬性,而這個(gè)屬性是用一個(gè)自啟動(dòng)函數(shù)傳入的。每次激活函數(shù)的時(shí)候直接在自已的活動(dòng)對(duì)象中去搜索到num就返回了,不再去父級(jí)變量中搜索了。這樣就能結(jié)果問題了。
6 javascript 代碼執(zhí)行過程
javascript中的function的一些東西這里就不說了。
關(guān)于function對(duì)象的[[scope]]是一個(gè)內(nèi)部屬性。
Functiion對(duì)象在創(chuàng)建的時(shí)候會(huì)自動(dòng)創(chuàng)建一個(gè)[[scope]]內(nèi)部屬性,且只有js引擎才能夠去訪問。同時(shí)也會(huì)創(chuàng)建一個(gè)scope chian的作用域鏈。且[[scope]]鏈接到scope china中。
3function add(){
Var name = “hello”;
}
那么如上:
Function add 在創(chuàng)建的時(shí)候就會(huì)創(chuàng)建一個(gè)[[scope]]的屬性,且指向scope chian.由于是在全局的環(huán)境中,那么scope chian剛指向window action object.(這里不是很清楚).

執(zhí)行此函數(shù)時(shí)會(huì)創(chuàng)建一個(gè)稱為“運(yùn)行期上下文(execution context)”的內(nèi)部對(duì)象,運(yùn)行期上下文定義了函數(shù)執(zhí)行時(shí)的環(huán)境。每個(gè)運(yùn)行期上下文都有自己的作用域鏈,用于標(biāo)識(shí)符解析,當(dāng)運(yùn)行期上下文被創(chuàng)建時(shí),而它的作用域鏈初始化為當(dāng)前運(yùn)行函數(shù)的[[Scope]]所包含的對(duì)象。
運(yùn)行期上下文的代碼被分成兩個(gè)基本的階段來處理:
進(jìn)入執(zhí)行上下文
執(zhí)行代碼
變量對(duì)象的修改變化與這兩個(gè)階段緊密相關(guān)。
注:這2個(gè)階段的處理是一般行為,和上下文的類型無關(guān)(也就是說,在全局上下文和函數(shù)上下文中的表現(xiàn)是一樣的)。
6.1 進(jìn)入執(zhí)行上下文
當(dāng)進(jìn)入執(zhí)行上下文(代碼執(zhí)行之前)時(shí),VO/AO里已經(jīng)包含了下列屬性(前面已經(jīng)說了):
函數(shù)的所有形參(如果我們是在函數(shù)執(zhí)行上下文中)
— 由名稱和對(duì)應(yīng)值組成的一個(gè)變量對(duì)象的屬性被創(chuàng)建;沒有傳遞對(duì)應(yīng)參數(shù)的話,那么由名稱和undefined值組成的一種變量對(duì)象的屬性也將被創(chuàng)建。
所有函數(shù)聲明(FunctionDeclaration,FD)
—由名稱和對(duì)應(yīng)值(函數(shù)對(duì)象(function-object))組成一個(gè)變量對(duì)象的屬性被創(chuàng)建;如果變量對(duì)象已經(jīng)存在相同名稱的屬性,則完全替換這個(gè)屬性。
所有變量聲明(var,VariableDeclaration)
— 由名稱和對(duì)應(yīng)值(undefined)組成一個(gè)變量對(duì)象的屬性被創(chuàng)建;如果變量名稱跟已經(jīng)聲明的形式參數(shù)或函數(shù)相同,則變量聲明不會(huì)干擾已經(jīng)存在的這類屬性。
讓我們看一個(gè)例子:
7function test(a, b) {
var c =10;
function d(){}
var e =function _e() {};
(function x(){});
}
test(10);// call
當(dāng)進(jìn)入帶有參數(shù)10的test函數(shù)上下文時(shí),AO表現(xiàn)為如下:
7AO(test) = {
a:10,
b: undefined,
c: undefined,
d:
e: undefined
};
注意,AO里并不包含函數(shù)“x”。這是因?yàn)椤皒”是一個(gè)函數(shù)表達(dá)式(FunctionExpression, 縮寫為 FE) 而不是函數(shù)聲明,函數(shù)表達(dá)式不會(huì)影響VO。 不管怎樣,函數(shù)“_e” 同樣也是函數(shù)表達(dá)式,但是就像我們下面將看到的那樣,因?yàn)樗峙浣o了變量“e”,所以它可以通過名稱“e”來訪問。
這之后,將進(jìn)入處理上下文代碼的第二個(gè)階段— 執(zhí)行代碼。
6.2 代碼執(zhí)行
這個(gè)周期內(nèi),AO/VO已經(jīng)擁有了屬性(不過,并不是所有的屬性都有值,大部分屬性的值還是系統(tǒng)默認(rèn)的初始值undefined )。
還是前面那個(gè)例子, AO/VO在代碼解釋期間被修改如下:
2AO['c'] =10;
AO['e'] = ;
再次注意,因?yàn)镕unctionExpression“_e”保存到了已聲明的變量“e”上,所以它仍然存在于內(nèi)存中。而FunctionExpression “x”卻不存在于AO/VO中,也就是說如果我們想嘗試調(diào)用“x”函數(shù),不管在函數(shù)定義之前還是之后,都會(huì)出現(xiàn)一個(gè)錯(cuò)誤“x is not defined”,未保存的函數(shù)表達(dá)式只有在它自己的定義或遞歸中才能被調(diào)用。
另一個(gè)經(jīng)典例子:
6alert(x);// function
var x =10;
alert(x);// 10
x =20;
function x() {};
alert(x);// 20
為什么第一個(gè)alert “x” 的返回值是function,而且它還是在“x” 聲明之前訪問的“x” 的?為什么不是10或20呢?因?yàn)?,根?jù)規(guī)范函數(shù)聲明是在當(dāng)進(jìn)入上下文時(shí)填入的; 同意周期,在進(jìn)入上下文的時(shí)候還有一個(gè)變量聲明“x”,那么正如我們?cè)谏弦粋€(gè)階段所說,變量聲明在順序上跟在函數(shù)聲明和形式參數(shù)聲明之后,而且在這個(gè)進(jìn)入上下文階段,變量聲明不會(huì)干擾VO中已經(jīng)存在的同名函數(shù)聲明或形式參數(shù)聲明,因此,在進(jìn)入上下文時(shí),VO的結(jié)構(gòu)如下:
10VO = {};
VO['x'] =
// 找到var x = 10;
// 如果functionx沒有已經(jīng)聲明的話
// 這時(shí)候x的值應(yīng)該是undefined
// 但是這個(gè)case里變量聲明沒有影響同名的function的值
VO['x'] =
緊接著,在執(zhí)行代碼階段,VO做如下修改:
VO['x'] =10;
VO['x'] =20;
我們可以在第二、三個(gè)alert看到這個(gè)效果。
在下面的例子里我們可以再次看到,變量是在進(jìn)入上下文階段放入VO中的。(因?yàn)?,雖然else部分代碼永遠(yuǎn)不會(huì)執(zhí)行,但是不管怎樣,變量“b”仍然存在于VO中。)
7if(true) {
var a =1;
}else{
var b =2;
}
alert(a);//
alert(b);// undefined,不是b沒有聲明,而是b的值是undefined

7 關(guān)于變量
通常,各類文章和JavaScript相關(guān)的書籍都聲稱:“不管是使用var關(guān)鍵字(在全局上下文)還是不使用var關(guān)鍵字(在任何地方),都可以聲明一個(gè)變量”。請(qǐng)記住,這是錯(cuò)誤的概念:
任何時(shí)候,變量只能通過使用var關(guān)鍵字才能聲明。
上面的賦值語句:
a= 10;
這僅僅是給全局對(duì)象創(chuàng)建了一個(gè)新屬性(但它不是變量)?!安皇亲兞俊辈⒉皇钦f它不能被改變,而是指它不符合ECMAScript規(guī)范中的變量概念,所以它“不是變量”(它之所以能成為全局對(duì)象的屬性,完全是因?yàn)閂O(globalContext) === global,大家還記得這個(gè)吧?)。
讓我們通過下面的實(shí)例看看具體的區(qū)別吧:
4alert(a);// undefined
alert(b);// b 沒有聲明
b =10;
var a =20;
所有根源仍然是VO和進(jìn)入上下文階段和代碼執(zhí)行階段:
進(jìn)入上下文階段:
3VO = {
a: undefined
};
我們可以看到,因?yàn)椤癰”不是一個(gè)變量,所以在這個(gè)階段根本就沒有“b”,“b”將只在代碼執(zhí)行階段才會(huì)出現(xiàn)(但是在我們這個(gè)例子里,還沒有到那就已經(jīng)出錯(cuò)了)。
讓我們改變一下例子代碼:
4alert(a);// undefined, 這個(gè)大家都知道,
b =10;
alert(b);// 10, 代碼執(zhí)行階段創(chuàng)建
var a =20;
關(guān)于變量,還有一個(gè)重要的知識(shí)點(diǎn)。變量相對(duì)于簡(jiǎn)單屬性來說,變量有一個(gè)特性(attribute):{DontDelete},這個(gè)特性的含義就是不能用delete操作符直接刪除變量屬性。
13a =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
但是這個(gè)規(guī)則在有個(gè)上下文里不起走樣,那就是eval上下文,變量沒有{DontDelete}特性。
eval('var a = 10;');
alert(window.a);// 10
alert(delete a);// true
alert(window.a);// undefined
使用一些調(diào)試工具(例如:Firebug)的控制臺(tái)測(cè)試該實(shí)例時(shí),請(qǐng)注意,F(xiàn)irebug同樣是使用eval來執(zhí)行控制臺(tái)里你的代碼。因此,變量屬性同樣沒有{DontDelete}特性,可以被刪除。
如果能認(rèn)真看完博客,相信你一定對(duì)javascript很感興趣。
文檔如果對(duì)你有一絲絲的幫助,那么恭喜。