一、作用域Scope和上下文Context
????在javascript中,作用域scope和上下文context是兩個不同的概念。每個函數(shù)調(diào)用都會伴隨著scope和context,從本質(zhì)上來說,scope是和函數(shù)綁定的,而context是基于對象的。即scope用于在函數(shù)調(diào)用時提供變量訪問,且每次函數(shù)調(diào)用時,都不同;而context始終是關(guān)鍵詞this
的值,它指向當(dāng)前執(zhí)行代碼所屬的對象。
scope 作用域
????在前一篇的“javascript變量”部分討論了javascript的作用域,分為全局和局部,且javascript中不存在塊作用域。
** 'this' context 上下文**
????context 經(jīng)常被函數(shù)所調(diào)用的方式所決定。(1)當(dāng)函數(shù)被作為一個對象的方法調(diào)用時,this 被設(shè)置為該函數(shù)所屬的對象。如
var obj = {
foo: function() {
return this;
}
};
obj.foo() === obj; // true。 this指向obj對象
(2)當(dāng)使用new關(guān)鍵字去創(chuàng)建一個新的函數(shù)對象時,this的值也被設(shè)置為新創(chuàng)建的函數(shù)對象。比如
function foo() {
alert(this);
}
foo() // window
new foo() // foo
(3)當(dāng)函數(shù)被普通調(diào)用時,this被為全局contex或者瀏覽器的window對象。比如
function foo() {
alert(this);
}
foo() // window
二、函數(shù)生命周期
????函數(shù)生命周期可以分為創(chuàng)建和執(zhí)行兩個階段。
????在函數(shù)創(chuàng)建階段,JS解析引擎進(jìn)行預(yù)解析,會將函數(shù)聲明提前,同時將該函數(shù)放到全局作用域中或當(dāng)前函數(shù)的上一級函數(shù)的局部作用域中。
????在函數(shù)執(zhí)行階段,JS解析引擎會將當(dāng)前函數(shù)的局部變量和內(nèi)部函數(shù)進(jìn)行聲明提前,然后再執(zhí)行業(yè)務(wù)代碼,當(dāng)函數(shù)執(zhí)行完退出時,釋放該函數(shù)的執(zhí)行上下文,并注銷該函數(shù)的局部變量。
三、變量對象
VO 和 AO
????VO (Variable Object)變量對象,對應(yīng)的是函數(shù)創(chuàng)建階段,JS解析引擎進(jìn)行預(yù)解析時,所有變量和函數(shù)的聲明(即在JS引擎的預(yù)解析階段,就確定了VO的內(nèi)容,只不過此時大部分屬性的值都是undefined)。VO與執(zhí)行上下文相關(guān),知道自己的數(shù)據(jù)存儲在哪里,并且知道如何訪問。VO是一個與執(zhí)行上下文相關(guān)的特殊對象,它存儲著在上下文中聲明的以下內(nèi)容:
(1)變量 (var, 變量聲明);
(2)函數(shù)聲明 (FunctionDeclaration, 縮寫為FD);
(3)函數(shù)的形參
function add(a,b){
var sum = a + b;
function say(){
alert(sum);
}
return sum;
}
// sum,say,a,b 組合的對象就是VO,不過該對象的值基本上都是undefined
????AO(Activation Object)對應(yīng)的是函數(shù)執(zhí)行階段,當(dāng)函數(shù)被調(diào)用執(zhí)行時,會創(chuàng)建一個執(zhí)行上下文,該執(zhí)行上下文包含了函數(shù)所需的所有變量,該變量共同組成了一個新的對象就是Activation Object。該對象包括了:
(1)函數(shù)的所有局部變量
(2)函數(shù)的所有命名參數(shù)聲明(Function Declaration)
(3)函數(shù)的參數(shù)集合
function add(a,b){
var sum = a + b;
var x = 10;
function say(){
alert(sum);
}
return sum;
}
add(4,5);
// AO = {
// arguments : [4,5],
// a : 4,
// b : 5,
// x: undefined
// say : <reference to function>,
// sum : undefined
// }
更詳細(xì)的關(guān)于變量對象VO的知識,請訪問:http://www.cnblogs.com/TomXu/archive/2012/01/16/2309728.html
四、執(zhí)行上下文
????執(zhí)行上下文(execution context)是ECMAScript規(guī)范中用來描述 JavaScript 代碼執(zhí)行的抽象概念。所有的 JavaScript 代碼都是在某個執(zhí)行上下文中運(yùn)行的。在當(dāng)前執(zhí)行上下文中調(diào)用 function會進(jìn)入一個新的執(zhí)行上下文。該function調(diào)用結(jié)束后會返回到原來的執(zhí)行上下文中。如果function在調(diào)用過程中拋出異常,并且沒有將其捕獲,有可能從多個執(zhí)行上下文中退出。在function調(diào)用過程中,也可能調(diào)用其他的function,從而進(jìn)入新的執(zhí)行上下文,由此形成一個執(zhí)行上下文棧。
????每個執(zhí)行上下文都與一個作用域鏈(scope chain)關(guān)聯(lián)起來。該作用域鏈用來在function執(zhí)行時求出標(biāo)識符(identifier)的值。該鏈中包含多個對象,在對標(biāo)識符進(jìn)行求值的過程中,會從鏈?zhǔn)椎膶ο箝_始,然后依次查找后面的對象,直到在某個對象中找到與標(biāo)識符名稱相同的屬性。在每個對象中進(jìn)行屬性查找時,會使用該對象的prototype鏈。在一個執(zhí)行上下文中,與其關(guān)聯(lián)的作用域鏈只會被with語句和catch 子句影響。
執(zhí)行上下文屬性
????每個執(zhí)行上下文都有三個重要的屬性,變量對象(Variable Object), 作用域鏈(Scope Chain)和this,當(dāng)然還有一些其他屬性。
![][3]
????當(dāng)一段javascript代碼被執(zhí)行的時候,javascript解釋器會創(chuàng)建并使用Execution Context,這里有兩個階段:
(1)創(chuàng)建階段(當(dāng)函數(shù)被調(diào)用,但開始執(zhí)行內(nèi)部代碼之前)
(a) 創(chuàng)建 Scope Chain
(b) 創(chuàng)建VO/AO (函數(shù)內(nèi)部變量聲明、函數(shù)聲明、函數(shù)參數(shù))
(c) 設(shè)置this值
(2)激活階段/代碼執(zhí)行階段
(a) 設(shè)置變量的值、函數(shù)的引用,然后解釋/執(zhí)行代碼。
在階段(1)(b)創(chuàng)建VO/AO這一步,解釋器主要做了以下事情:
(1)根據(jù)函數(shù)的參數(shù),創(chuàng)建并初始化參數(shù)列表
(2)掃描函數(shù)內(nèi)部代碼,查找函數(shù)聲明。對于所有找到的內(nèi)部函數(shù)聲明,將函數(shù)名和函數(shù)引用存儲 VO/AO中;如果 VO/AO中已經(jīng)有同名的函數(shù),那么就進(jìn)行覆蓋
(3)掃描函數(shù)內(nèi)部代碼,查找變量聲明。對于所有找到的變量聲明,將變量名存入VO/AO中,并初始化為 undefined;如果變量名稱和已經(jīng)聲明的形式參數(shù)或函數(shù)相同,則變量聲明不會干擾已經(jīng)存在的這類屬性(就是說變量無效)
比如以下代碼:
function foo(i) {
var a = 'hello';
var b = function privateB() {
};
function c() {
}
}
foo(22);
在“創(chuàng)建階段”,可以得到下面的 Execution Context object:
fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: undefined,
b: undefined
},
this: { ... }
}
在“激活/代碼執(zhí)行階段”,Execution Context object 被更新為:
fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: 'hello',
b: pointer to function privateB()
},
this: { ... }
}
????函數(shù)在定義時就會確定它的作用域和作用域鏈(靜態(tài)),只有在調(diào)用的時候才會創(chuàng)建一個執(zhí)行上下文,(1)其中包含了調(diào)用時的形參,函數(shù)內(nèi)的函數(shù)聲明與變量,同時創(chuàng)建活動對象AO;(2)并將AO壓入執(zhí)行上下文的作用域鏈的最前端,執(zhí)行上下文的作用域鏈?zhǔn)峭ㄟ^它正在調(diào)用的函數(shù)的[[scope]]屬性得到的(動態(tài));(3)執(zhí)行上下文對象中也包含this的屬性
五、作用域鏈 scope chain
????每個運(yùn)行上下文都有自己的變量對象,對于全局上下文,它是全局對象本身;對于函數(shù),它是活動對象。作用域鏈?zhǔn)沁\(yùn)行上下文所有變量對象(包括父變量對象)的列表。此鏈表用于查詢標(biāo)識符。
var x = 10;
function foo() {
var y = 20;
function bar() {
alert(x + y);
}
return bar;
}
foo()(); // 30
上面的例子中, bar 上下文的作用域鏈包括 AO(bar) --> AO(foo) -- > VO(global).
作用域鏈如何構(gòu)造的
????上面提到,作用域鏈Scope Chain是執(zhí)行上下文Execution Context的一個屬性。它是在函數(shù)被執(zhí)行時,通過被執(zhí)行函數(shù)的[[scope]]屬性得到。
????函數(shù)創(chuàng)建時:在javascript中,函數(shù)也是一個對象,它有一個屬性[[scope]],該屬性是在函數(shù)被創(chuàng)建時寫入,它是該函數(shù)對象的所有父變量對象的層級鏈,它存在于函數(shù)這個對象中,直到函數(shù)銷毀。
????函數(shù)執(zhí)行時:創(chuàng)建執(zhí)行上下文Execution context, 執(zhí)行上下文Execution context 把 AO 放在 函數(shù)[[scope]]最前面作為該執(zhí)行上下文的Scope chain。
即 Scope chain(運(yùn)行上下文的屬性,動態(tài)) = AO|VO(運(yùn)行上下文的屬性,動態(tài)) + [[Scope]](函數(shù)的屬性,靜態(tài))
一個例子
var x = 10;
function foo() {
var y = 20;
function bar() {
var z = 30;
alert(x + y + z);
}
bar();
}
foo(); // 60
全局上下文的變量對象是:
globalContext.VO === Global = {
x: 10
foo: <reference to function>
};
在“foo”創(chuàng)建時,“foo”的[[scope]]屬性是:
foo.[[Scope]] = [
globalContext.VO
];
在“foo”激活時(進(jìn)入上下文),“foo”上下文的活動對象是:
fooContext.AO = {
y: 20,
bar: <reference to function>
};
“foo”上下文的作用域鏈為:
fooContext.Scope = fooContext.AO + foo.[[Scope]] // i.e.:
fooContext.Scope = [
fooContext.AO,
globalContext.VO
];
內(nèi)部函數(shù)“bar”創(chuàng)建時,其[[scope]]為:
bar.[[Scope]] = [
fooContext.AO,
globalContext.VO
];
在“bar”激活時,“bar”上下文的活動對象為:
barContext.AO = {
z: 30
};
“bar”上下文的作用域鏈為:
barContext.Scope = barContext.AO + bar.[[Scope]] // i.e.:
barContext.Scope = [
barContext.AO,
fooContext.AO,
globalContext.VO
];
對“x”、“y”、“z”的標(biāo)識符解析如下:
"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
基于作用域鏈的變量查詢
????在代碼執(zhí)行時需要訪問某個變量值時,JS引擎會在Execution context的scope chain中從頭到尾查找,直到在scope chain的某個元素中找到或者無法找到該變量。