javascript中的[[scope]]、scope chain、execution context!

來源:仗劍走天涯!

關(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)境的作用域中處于第二位,如圖:

后臺(tái)的每個(gè)執(zhí)行環(huán)境都有一個(gè)表示變量的對(duì)象--變量對(duì)象。全局環(huán)境的變量對(duì)象始終存在,而像compare()函數(shù)這樣的局部環(huán)境的變量對(duì)象則只在函數(shù)執(zhí)行過程中存在。在創(chuàng)建compare()函數(shù)(注意,這里是創(chuàng)建)時(shí),會(huì)創(chuàng)建一個(gè)預(yù)先包含全局變量對(duì)象的作用域鏈,這個(gè)作用域鏈被保存在內(nèi)部的[[Scope]]屬性中。當(dāng)調(diào)用compare函數(shù)時(shí),會(huì)為函數(shù)創(chuàng)建一個(gè)執(zhí)行環(huán)境,然后通過復(fù)制函數(shù)的[[Scope]]屬性中的對(duì)象構(gòu)建起執(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});

在匿名函數(shù)從createComparisonFunction()中被返回后,它的作用域鏈被初始化為包含createComparisonFunction()函數(shù)的活動(dòng)對(duì)象和全局變量對(duì)象。這樣createComparisonFunction()函數(shù)執(zhí)行完畢后其活動(dòng)對(duì)象也不會(huì)被銷毀,因?yàn)槟涿瘮?shù)的作用域鏈仍然在引用這個(gè)活動(dòng)對(duì)象。換句話說,createComparisonFunction()函數(shù)返回后,其執(zhí)行環(huán)境的作用域鏈會(huì)被銷毀,但它的活動(dòng)對(duì)象仍會(huì)留在內(nèi)存中;直到匿名函數(shù)被銷毀后,createComparisonFunction()的活動(dòng)對(duì)象才會(huì)被銷毀。

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.(這里不是很清楚).

這么以上的情況都是要定義的時(shí)候完成的,也可以說是在js解釋的時(shí)候完成的。

執(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ì)你有一絲絲的幫助,那么恭喜。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容