- 相關(guān)推薦
JavaScript作用域和作用域鏈
作用域是JavaScript最重要的概念之一,想要學(xué)好JavaScript就需要理解JavaScript作用域和作用域鏈的工作原理。今天這篇文章對JavaScript作用域和作用域鏈作簡(jiǎn)單的介紹,希望能幫助大家更好的學(xué)習JavaScript。
JavaScript作用域
任何程序設計語(yǔ)言都有作用域的概念,簡(jiǎn)單的說(shuō),作用域就是變量與函數的可訪(fǎng)問(wèn)范圍,即作用域控制著(zhù)變量與函數的可見(jiàn)性和生命周期。在JavaScript中,變量的作用域有全局作用域和局部作用域兩種。
1. 全局作用域(Global Scope)
在代碼中任何地方都能訪(fǎng)問(wèn)到的對象擁有全局作用域,一般來(lái)說(shuō)一下幾種情形擁有全局作用域:
(1)最外層函數和在最外層函數外面定義的變量擁有全局作用域,例如:
復制代碼 代碼如下:
var authorName="山邊小溪";
function doSomething(){
var blogName="夢(mèng)想天空";
function innerSay(){
alert(blogName);
}
innerSay();
}
alert(authorName); //山邊小溪
alert(blogName); //腳本錯誤
doSomething(); //夢(mèng)想天空
innerSay() //腳本錯誤
(2)所有末定義直接賦值的變量自動(dòng)聲明為擁有全局作用域,例如:
復制代碼 代碼如下:
function doSomething(){
var authorName="山邊小溪";
blogName="夢(mèng)想天空";
alert(authorName);
}
alert(blogName); //夢(mèng)想天空
alert(authorName); //腳本錯誤
變量blogName擁有全局作用域,而authorName在函數外部無(wú)法訪(fǎng)問(wèn)到。
(3)所有window對象的屬性擁有全局作用域
一般情況下,window對象的內置屬性都都擁有全局作用域,例如window.name、window.location、window.top等等。
1. 局部作用域(Local Scope)
和全局作用域相反,局部作用域一般只在固定的代碼片段內可訪(fǎng)問(wèn)到,最常見(jiàn)的例如函數內部,所有在一些地方也會(huì )看到有人把這種作用域成為函數作用域,例如下列代碼中的blogName和函數innerSay都只擁有局部作用域。
復制代碼 代碼如下:
function doSomething(){
var blogName="夢(mèng)想天空";
function innerSay(){
alert(blogName);
}
innerSay();
}
alert(blogName); //腳本錯誤
innerSay(); //腳本錯誤
作用域鏈(Scope Chain)
在JavaScript中,函數也是對象,實(shí)際上,JavaScript里一切都是對象。函數對象和其它對象一樣,擁有可以通過(guò)代碼訪(fǎng)問(wèn)的屬性和一系列僅供JavaScript引擎訪(fǎng)問(wèn)的內部屬性。其中一個(gè)內部屬性是[[Scope]],由ECMA-262標準第三版定義,該內部屬性包含了函數被創(chuàng )建的作用域中對象的集合,這個(gè)集合被稱(chēng)為函數的作用域鏈,它決定了哪些數據能被函數訪(fǎng)問(wèn)。
當一個(gè)函數創(chuàng )建后,它的作用域鏈會(huì )被創(chuàng )建此函數的作用域中可訪(fǎng)問(wèn)的數據對象填充。例如定義下面這樣一個(gè)函數:
復制代碼 代碼如下:
function add(num1,num2) {
var sum = num1 + num2;
return sum;
}
在函數add創(chuàng )建時(shí),它的作用域鏈中會(huì )填入一個(gè)全局對象,該全局對象包含了所有全局變量,如下圖所示(注意:圖片只例舉了全部變量中的一部分):
函數add的作用域將會(huì )在執行時(shí)用到。例如執行如下代碼:
復制代碼 代碼如下:
var total = add(5,10);
執行此函數時(shí)會(huì )創(chuàng )建一個(gè)稱(chēng)為“運行期上下文(execution context)”的內部對象,運行期上下文定義了函數執行時(shí)的環(huán)境。每個(gè)運行期上下文都有自己的作用域鏈,用于標識符解析,當運行期上下文被創(chuàng )建時(shí),而它的作用域鏈初始化為當前運行函數的[[Scope]]所包含的對象。
這些值按照它們出現在函數中的順序被復制到運行期上下文的作用域鏈中。它們共同組成了一個(gè)新的對象,叫“活動(dòng)對象(activation object)”,該對象包含了函數的所有局部變量、命名參數、參數集合以及this,然后此對象會(huì )被推入作用域鏈的前端,當運行期上下文被銷(xiāo)毀,活動(dòng)對象也隨之銷(xiāo)毀。新的作用域鏈如下圖所示:
在函數執行過(guò)程中,沒(méi)遇到一個(gè)變量,都會(huì )經(jīng)歷一次標識符解析過(guò)程以決定從哪里獲取和存儲數據。該過(guò)程從作用域鏈頭部,也就是從活動(dòng)對象開(kāi)始搜索,查找同名的標識符,如果找到了就使用這個(gè)標識符對應的變量,如果沒(méi)找到繼續搜索作用域鏈中的下一個(gè)對象,如果搜索完所有對象都未找到,則認為該標識符未定義。函數執行過(guò)程中,每個(gè)標識符都要經(jīng)歷這樣的搜索過(guò)程。
作用域鏈和代碼優(yōu)化
從作用域鏈的結構可以看出,在運行期上下文的作用域鏈中,標識符所在的位置越深,讀寫(xiě)速度就會(huì )越慢。如上圖所示,因為全局變量總是存在于運行期上下文作用域鏈的最末端,因此在標識符解析的時(shí)候,查找全局變量是最慢的。所以,在編寫(xiě)代碼的時(shí)候應盡量少使用全局變量,盡可能使用局部變量。一個(gè)好的經(jīng)驗法則是:如果一個(gè)跨作用域的對象被引用了一次以上,則先把它存儲到局部變量里再使用。例如下面的代碼:
復制代碼 代碼如下:
function changeColor(){
document.getElementById("btnChange").onclick=function(){
document.getElementById("targetCanvas").style.backgroundColor="red";
};
}
這個(gè)函數引用了兩次全局變量document,查找該變量必須遍歷整個(gè)作用域鏈,直到最后在全局對象中才能找到。這段代碼可以重寫(xiě)如下:
復制代碼 代碼如下:
function changeColor(){
var doc=document;
doc.getElementById("btnChange").onclick=function(){
doc.getElementById("targetCanvas").style.backgroundColor="red";
};
}
這段代碼比較簡(jiǎn)單,重寫(xiě)后不會(huì )顯示出巨大的性能提升,但是如果程序中有大量的全局變量被從反復訪(fǎng)問(wèn),那么重寫(xiě)后的代碼性能會(huì )有顯著(zhù)改善。
改變作用域鏈
函數每次執行時(shí)對應的運行期上下文都是獨一無(wú)二的,所以多次調用同一個(gè)函數就會(huì )導致創(chuàng )建多個(gè)運行期上下文,當函數執行完畢,執行上下文會(huì )被銷(xiāo)毀。每一個(gè)運行期上下文都和一個(gè)作用域鏈關(guān)聯(lián)。一般情況下,在運行期上下文運行的過(guò)程中,其作用域鏈只會(huì )被 with 語(yǔ)句和 catch 語(yǔ)句影響。
with語(yǔ)句是對象的快捷應用方式,用來(lái)避免書(shū)寫(xiě)重復代碼。例如:
復制代碼 代碼如下:
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語(yǔ)句來(lái)避免多次書(shū)寫(xiě)document,看上去更高效,實(shí)際上產(chǎn)生了性能問(wèn)題。
當代碼運行到with語(yǔ)句時(shí),運行期上下文的作用域鏈臨時(shí)被改變了。一個(gè)新的可變對象被創(chuàng )建,它包含了參數指定的對象的所有屬性。這個(gè)對象將被推入作用域鏈的頭部,這意味著(zhù)函數的所有局部變量現在處于第二個(gè)作用域鏈對象中,因此訪(fǎng)問(wèn)代價(jià)更高了。如下圖所示:
因此在程序中應避免使用with語(yǔ)句,在這個(gè)例子中,只要簡(jiǎn)單的把document存儲在一個(gè)局部變量中就可以提升性能。
另外一個(gè)會(huì )改變作用域鏈的是try-catch語(yǔ)句中的catch語(yǔ)句。當try代碼塊中發(fā)生錯誤時(shí),執行過(guò)程會(huì )跳轉到catch語(yǔ)句,然后把異常對象推入一個(gè)可變對象并置于作用域的頭部。在catch代碼塊內部,函數的所有局部變量將會(huì )被放在第二個(gè)作用域鏈對象中。示例代碼:
復制代碼 代碼如下:
try{
doSomething();
}catch(ex){
alert(ex.message); //作用域鏈在此處改變
}
請注意,一旦catch語(yǔ)句執行完畢,作用域鏈機會(huì )返回到之前的狀態(tài)。try-catch語(yǔ)句在代碼調試和異常處理中非常有用,因此不建議完全避免。你可以通過(guò)優(yōu)化代碼來(lái)減少catch語(yǔ)句對性能的影響。一個(gè)很好的模式是將錯誤委托給一個(gè)函數處理,例如:
復制代碼 代碼如下:
try{
doSomething();
}catch(ex){
handleError(ex); //委托給處理器方法
}
優(yōu)化后的代碼,handleError方法是catch子句中唯一執行的代碼。該函數接收異常對象作為參數,這樣你可以更加靈活和統一的處理錯誤。由于只執行一條語(yǔ)句,且沒(méi)有局部變量的訪(fǎng)問(wèn),作用域鏈的臨時(shí)改變就不會(huì )影響代碼性能了。
【JavaScript作用域和作用域鏈】相關(guān)文章:
javascript跨域訪(fǎng)問(wèn)的方法07-09
供應鏈管理對企業(yè)的作用06-25
外鏈的表現形式及作用09-07
項目管理對供應鏈管理的作用07-22
Word域功能解析06-17
理解JavaScript原型鏈教程09-02
陳皮的功效和作用10-05
玉竹的作用和功效07-03
溝通的作用和意義06-26