- 前天被問到作用域鏈的知識(shí),感覺有個(gè)大概的認(rèn)識(shí),但是轉(zhuǎn)化為語言就無法調(diào)理清楚地講述出來舀瓢,回來后決定惡補(bǔ)功課,在此做個(gè)筆記奏纪。
- 注:筆記有部分內(nèi)容摘抄自其他技術(shù)博客,如覺得有被侵權(quán)斩启,我會(huì)立馬刪除序调。
變量對(duì)象
- 首先要理解變量對(duì)象是什么,代碼在執(zhí)行時(shí)都會(huì)經(jīng)歷先創(chuàng)建再執(zhí)行的過程兔簇,在創(chuàng)建時(shí)发绢,就會(huì)建立一個(gè)變量對(duì)象,里面包括arguments男韧、本函數(shù)內(nèi)部聲明的函數(shù)朴摊、本函數(shù)內(nèi)部聲明的變量、函數(shù)形參等此虑。
- 用圖像表示就是:
再舉個(gè)例子:
function outerFun (arg1, arg2) {
var outerV1 = 1
var outerV2 = 2
function innerFun1 () {
var innerV1 = 3;
var innerV2 = 4;
console.log('i am innerFun1...')
}
function innerFun2 () {
console.log('i am innerFun2...')
}
function outerV2 () {
return 'i am outerV2'
}
}
outerFun()
活動(dòng)對(duì)象
- 個(gè)人覺得就是指的變量對(duì)象,只不過變量對(duì)象是在創(chuàng)建時(shí)候的術(shù)語口锭,而活動(dòng)對(duì)象是指在執(zhí)行狀態(tài)的對(duì)象朦前。
[[scope]]屬性
[[scope]]是所有外層函數(shù)(執(zhí)行環(huán)境)的變量對(duì)象的層級(jí)鏈。
[[scope]]屬性在函數(shù)創(chuàng)建時(shí)被存儲(chǔ)鹃操,永遠(yuǎn)不變韭寸,直到函數(shù)被銷毀。函數(shù)可以不被調(diào)用荆隘,但該屬性一直存在恩伺。
與作用域鏈相比,作用域鏈?zhǔn)腔顒?dòng)的執(zhí)行環(huán)境的一個(gè)屬性椰拒,而[[scope]]是函數(shù)的屬性晶渠。
舉個(gè)例子:
function add(num1,num2) { var sum = num1 + num2; return sum; }
add函數(shù)是定義在全局環(huán)境里面的,所以在創(chuàng)建時(shí)燃观,它的作用域鏈里面的第一個(gè)指針會(huì)指向一個(gè)全局對(duì)象褒脯,該全局對(duì)象包含了所有的全局變量。
作用域鏈
- 剛剛也說了缆毁,作用域鏈?zhǔn)菆?zhí)行環(huán)境的一個(gè)屬性番川,當(dāng)函數(shù)開始執(zhí)行時(shí),就要在執(zhí)行環(huán)境中建立作用域鏈,首先通過復(fù)制[[scope]]屬性中的對(duì)象颁督,來構(gòu)建起執(zhí)行環(huán)境的初始作用域践啄。然后將當(dāng)前函數(shù)的活動(dòng)對(duì)象(包含本函數(shù)內(nèi)部的變量、this沉御、arguments等對(duì)象)等推入作用域鏈的頂端屿讽。
- 繼續(xù)上面的例子:
- 當(dāng)開始執(zhí)行函數(shù)
add(5,10)
時(shí),會(huì)創(chuàng)建一個(gè)稱為“執(zhí)行上下文(execution context)”的內(nèi)部對(duì)象嚷节,也就是add.execution_context聂儒,它是屬于add的一個(gè)對(duì)象。 - 這個(gè)對(duì)象有一個(gè)Scope Chain指針指向作用域鏈硫痰,這個(gè)作用域鏈根據(jù)[[scope]]來進(jìn)行初始化衩婚,然后將自己的活動(dòng)對(duì)象也推入作用域鏈。( 該對(duì)象包含了該函數(shù)環(huán)境中的所有局部變量效斑、函數(shù)方法非春、命名參數(shù)以及this,arguments等)
函數(shù)每執(zhí)行一次缓屠,它的執(zhí)行上下文都會(huì)被重新創(chuàng)建奇昙,到函數(shù)運(yùn)行結(jié)束時(shí),又再銷毀敌完。
對(duì)于在函數(shù)內(nèi)部作為返回值被返回的閉包函數(shù)储耐,在其被返回以后,它的作用域鏈就被初始化了滨溉,此時(shí)包含外層函數(shù)的活動(dòng)對(duì)象什湘、全局變量對(duì)象,至于它自己的活動(dòng)對(duì)象晦攒,是在調(diào)用的時(shí)候才建立的闽撤。最終它的作用域鏈里面會(huì)有3個(gè)對(duì)象,依次是:自己的活動(dòng)對(duì)象脯颜、外層行數(shù)的活動(dòng)對(duì)象哟旗、全局變量對(duì)象。
- 在函數(shù)執(zhí)行過程中栋操,每遇到一個(gè)變量闸餐,都會(huì)經(jīng)歷一次標(biāo)識(shí)符解析過程以決定從哪里獲取和存儲(chǔ)數(shù)據(jù)。該過程從作用域鏈頭部讼庇,也就是從活動(dòng)對(duì)象開始搜索绎巨,查找同名的標(biāo)識(shí)符,如果找到了就使用這個(gè)標(biāo)識(shí)符對(duì)應(yīng)的變量蠕啄,如果沒找到繼續(xù)搜索作用域鏈中的下一個(gè)對(duì)象场勤,如果搜索完所有對(duì)象都未找到戈锻,則認(rèn)為該標(biāo)識(shí)符未定義。函數(shù)執(zhí)行過程中和媳,每個(gè)標(biāo)識(shí)符都要經(jīng)歷這樣的搜索過程格遭。
改變作用域鏈
- 函數(shù)每次執(zhí)行時(shí)對(duì)應(yīng)的運(yùn)行期上下文都是獨(dú)一無二的,所以多次調(diào)用同一個(gè)函數(shù)就會(huì)導(dǎo)致創(chuàng)建多個(gè)運(yùn)行期上下文留瞳,當(dāng)函數(shù)執(zhí)行完畢拒迅,執(zhí)行上下文會(huì)被銷毀。每一個(gè)運(yùn)行期上下文都和一個(gè)作用域鏈關(guān)聯(lián)她倘。一般情況下璧微,在運(yùn)行期上下文運(yùn)行的過程中,其作用域鏈只會(huì)被 with 語句和 catch 語句影響硬梁。
- with語句是對(duì)象的快捷應(yīng)用方式前硫,用來避免書寫重復(fù)代碼。例如:
function initUI(){
with(document){
var bd=body,
links=getElementsByTagName("a"),
i=0,
len=links.length;
while(i < len){
update(links[i++]);
}
getElementById("btnInit").onclick=function(){
doSomething();
};
}
} - 這里使用width語句來避免多次書寫document荧止,看上去更高效屹电,實(shí)際上產(chǎn)生了性能問題。
- 當(dāng)代碼運(yùn)行到with語句時(shí)跃巡,運(yùn)行期上下文的作用域鏈臨時(shí)被改變了危号。一個(gè)新的可變對(duì)象被創(chuàng)建,它包含了參數(shù)指定的對(duì)象的所有屬性素邪。這個(gè)對(duì)象將被推入作用域鏈的頭部外莲,這意味著函數(shù)的所有局部變量現(xiàn)在處于第二個(gè)作用域鏈對(duì)象中,因此訪問代價(jià)更高了兔朦。如下圖所示:
因此在程序中應(yīng)避免使用with語句苍狰,在這個(gè)例子中,只要簡(jiǎn)單的把document存儲(chǔ)在一個(gè)局部變量中就可以提升性能烘绽。
另外一個(gè)會(huì)改變作用域鏈的是try-catch語句中的catch語句。當(dāng)try代碼塊中發(fā)生錯(cuò)誤時(shí)俐填,執(zhí)行過程會(huì)跳轉(zhuǎn)到catch語句安接,然后把異常對(duì)象推入一個(gè)可變對(duì)象并置于作用域的頭部。在catch代碼塊內(nèi)部英融,函數(shù)的所有局部變量將會(huì)被放在第二個(gè)作用域鏈對(duì)象中盏檐。示例代碼:
try{ doSomething(); }catch(ex){ alert(ex.message); //作用域鏈在此處改變 }
請(qǐng)注意,一旦catch語句執(zhí)行完畢驶悟,作用域鏈機(jī)會(huì)返回到之前的狀態(tài)胡野。try-catch語句在代碼調(diào)試和異常處理中非常有用,因此不建議完全避免痕鳍。你可以通過優(yōu)化代碼來減少catch語句對(duì)性能的影響硫豆。一個(gè)很好的模式是將錯(cuò)誤委托給一個(gè)函數(shù)處理龙巨,例如:
try{ doSomething(); }catch(ex){ handleError(ex); //委托給處理器方法 }
優(yōu)化后的代碼,handleError方法是catch子句中唯一執(zhí)行的代碼熊响。該函數(shù)接收異常對(duì)象作為參數(shù)旨别,這樣你可以更加靈活和統(tǒng)一的處理錯(cuò)誤。由于只執(zhí)行一條語句汗茄,且沒有局部變量的訪問秸弛,作用域鏈的臨時(shí)改變就不會(huì)影響代碼性能了。