跟我學(xué)習(xí)javascript的作用域與作用域鏈
作用域是JavaScript最重要的概念之一,想要學(xué)好JavaScript就需要理解JavaScript作用域和作用域鏈的工作原理。今天這篇文章對(duì)JavaScript作用域和作用域鏈作簡(jiǎn)單的介紹,希望能幫助大家更好的學(xué)習(xí)JavaScript。
一、JavaScript作用域
任何程序設(shè)計(jì)語(yǔ)言都有作用域的概念,簡(jiǎn)單的說(shuō),作用域就是變量與函數(shù)的可訪問(wèn)范圍,即作用域控制著變量與函數(shù)的可見(jiàn)性和生命周期。在JavaScript中,變量的作用域有全局作用域和局部作用域兩種。
1. 全局作用域(Global Scope)
在代碼中任何地方都能訪問(wèn)到的對(duì)象擁有全局作用域,一般來(lái)說(shuō)以下幾種情形擁有全局作用域:
?。?)最外層函數(shù)和在最外層函數(shù)外面定義的變量擁有全局作用域,例如:
var authorName="小平果"; function doSomething(){ var blogName="你不知道的JavaScript"; function innerSay(){ alert(blogName); } innerSay(); } alert(authorName); //小平果 alert(blogName); //腳本錯(cuò)誤 doSomething(); //你不知道的JavaScript innerSay() //腳本錯(cuò)誤
?。?)所有末定義直接賦值的變量自動(dòng)聲明為擁有全局作用域,例如:
function doSomething(){ var authorName="小平果"; blogName="你不知道的JavaScript"; alert(authorName); } doSomething(); //小平果 alert(blogName); //你不知道的JavaScript alert(authorName); //腳本錯(cuò)誤
變量blogName擁有全局作用域,而authorName在函數(shù)外部無(wú)法訪問(wèn)到。
?。?)所有window對(duì)象的屬性擁有全局作用域
一般情況下,window對(duì)象的內(nèi)置屬性都擁有全局作用域,例如window.name、window.location、window.top等等。
2. 局部作用域(Local Scope)
和全局作用域相反,局部作用域一般只在固定的代碼片段內(nèi)可訪問(wèn)到,最常見(jiàn)的例如函數(shù)內(nèi)部,所有在一些地方也會(huì)看到有人把這種作用域稱為函數(shù)作用域,例如下列代碼中的blogName和函數(shù)innerSay都只擁有局部作用域。
function doSomething(){ var blogName="你不知道的JavaScript"; function innerSay(){ alert(blogName); } innerSay(); } alert(blogName); //腳本錯(cuò)誤 innerSay(); //腳本錯(cuò)誤
二、作用域鏈(Scope Chain)
在JavaScript中,函數(shù)也是對(duì)象,實(shí)際上,JavaScript里一切都是對(duì)象。函數(shù)對(duì)象和其它對(duì)象一樣,擁有可以通過(guò)代碼訪問(wèn)的屬性和一系列僅供JavaScript引擎訪問(wèn)的內(nèi)部屬性。其中一個(gè)內(nèi)部屬性是[[Scope]],由ECMA-262標(biāo)準(zhǔn)第三版定義,該內(nèi)部屬性包含了函數(shù)被創(chuàng)建的作用域中對(duì)象的集合,這個(gè)集合被稱為函數(shù)的作用域鏈,它決定了哪些數(shù)據(jù)能被函數(shù)訪問(wèn)。
當(dāng)一個(gè)函數(shù)創(chuàng)建后,它的作用域鏈會(huì)被創(chuàng)建此函數(shù)的作用域中可訪問(wèn)的數(shù)據(jù)對(duì)象填充。例如定義下面這樣一個(gè)函數(shù):
function add(num1,num2) { var sum = num1 + num2; return sum; }
在函數(shù)add創(chuàng)建時(shí),它的作用域鏈中會(huì)填入一個(gè)全局對(duì)象,該全局對(duì)象包含了所有全局變量,如下圖所示(注意:圖片只例舉了全部變量中的一部分):
函數(shù)add的作用域?qū)?huì)在執(zhí)行時(shí)用到。例如執(zhí)行如下代碼:
var total = add(5,10);
執(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ì)象。
這些值按照它們出現(xiàn)在函數(shù)中的順序被復(fù)制到運(yùn)行期上下文的作用域鏈中。它們共同組成了一個(gè)新的對(duì)象,叫“活動(dòng)對(duì)象(activation object)”,該對(duì)象包含了函數(shù)的所有局部變量、命名參數(shù)、參數(shù)集合以及this,然后此對(duì)象會(huì)被推入作用域鏈的前端,當(dāng)運(yùn)行期上下文被銷毀,活動(dòng)對(duì)象也隨之銷毀。新的作用域鏈如下圖所示:
在函數(shù)執(zhí)行過(guò)程中,沒(méi)遇到一個(gè)變量,都會(huì)經(jīng)歷一次標(biāo)識(shí)符解析過(guò)程以決定從哪里獲取和存儲(chǔ)數(shù)據(jù)。該過(guò)程從作用域鏈頭部,也就是從活動(dòng)對(duì)象開(kāi)始搜索,查找同名的標(biāo)識(shí)符,如果找到了就使用這個(gè)標(biāo)識(shí)符對(duì)應(yīng)的變量,如果沒(méi)找到繼續(xù)搜索作用域鏈中的下一個(gè)對(duì)象,如果搜索完所有對(duì)象都未找到,則認(rèn)為該標(biāo)識(shí)符未定義。函數(shù)執(zhí)行過(guò)程中,每個(gè)標(biāo)識(shí)符都要經(jīng)歷這樣的搜索過(guò)程。
三、作用域鏈和代碼優(yōu)化
從作用域鏈的結(jié)構(gòu)可以看出,在運(yùn)行期上下文的作用域鏈中,標(biāo)識(shí)符所在的位置越深,讀寫(xiě)速度就會(huì)越慢。如上圖所示,因?yàn)槿肿兞靠偸谴嬖谟谶\(yùn)行期上下文作用域鏈的最末端,因此在標(biāo)識(shí)符解析的時(shí)候,查找全局變量是最慢的。所以,在編寫(xiě)代碼的時(shí)候應(yīng)盡量少使用全局變量,盡可能使用局部變量。一個(gè)好的經(jīng)驗(yàn)法則是:如果一個(gè)跨作用域的對(duì)象被引用了一次以上,則先把它存儲(chǔ)到局部變量里再使用。例如下面的代碼:
function changeColor(){ document.getElementById("btnChange").onclick=function(){ document.getElementById("targetCanvas").style.backgroundColor="red"; }; }
這個(gè)函數(shù)引用了兩次全局變量document,查找該變量必須遍歷整個(gè)作用域鏈,直到最后在全局對(duì)象中才能找到。這段代碼可以重寫(xiě)如下:
function changeColor(){ var doc=document; doc.getElementById("btnChange").onclick=function(){ doc.getElementById("targetCanvas").style.backgroundColor="red"; }; }
這段代碼比較簡(jiǎn)單,重寫(xiě)后不會(huì)顯示出巨大的性能提升,但是如果程序中有大量的全局變量被從反復(fù)訪問(wèn),那么重寫(xiě)后的代碼性能會(huì)有顯著改善。
四、改變作用域鏈
函數(shù)每次執(zhí)行時(shí)對(duì)應(yīng)的運(yùn)行期上下文都是獨(dú)一無(wú)二的,所以多次調(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)行的過(guò)程中,其作用域鏈只會(huì)被 with 語(yǔ)句和 catch 語(yǔ)句影響。
with語(yǔ)句是對(duì)象的快捷應(yīng)用方式,用來(lái)避免書(shū)寫(xiě)重復(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語(yǔ)句來(lái)避免多次書(shū)寫(xiě)document,看上去更高效,實(shí)際上產(chǎn)生了性能問(wèn)題。
當(dāng)代碼運(yùn)行到with語(yǔ)句時(shí),運(yùn)行期上下文的作用域鏈臨時(shí)被改變了。一個(gè)新的可變對(duì)象被創(chuàng)建,它包含了參數(shù)指定的對(duì)象的所有屬性。這個(gè)對(duì)象將被推入作用域鏈的頭部,這意味著函數(shù)的所有局部變量現(xiàn)在處于第二個(gè)作用域鏈對(duì)象中,因此訪問(wèn)代價(jià)更高了。如下圖所示:
因此在程序中應(yīng)避免使用with語(yǔ)句,在這個(gè)例子中,只要簡(jiǎn)單的把document存儲(chǔ)在一個(gè)局部變量中就可以提升性能。
另外一個(gè)會(huì)改變作用域鏈的是try-catch語(yǔ)句中的catch語(yǔ)句。當(dāng)try代碼塊中發(fā)生錯(cuò)誤時(shí),執(zhí)行過(guò)程會(huì)跳轉(zhuǎn)到catch語(yǔ)句,然后把異常對(duì)象推入一個(gè)可變對(duì)象并置于作用域的頭部。在catch代碼塊內(nèi)部,函數(shù)的所有局部變量將會(huì)被放在第二個(gè)作用域鏈對(duì)象中。示例代碼:
try{ doSomething(); }catch(ex){ alert(ex.message); //作用域鏈在此處改變 }
請(qǐng)注意,一旦catch語(yǔ)句執(zhí)行完畢,作用域鏈機(jī)會(huì)返回到之前的狀態(tài)。try-catch語(yǔ)句在代碼調(diào)試和異常處理中非常有用,因此不建議完全避免。你可以通過(guò)優(yōu)化代碼來(lái)減少catch語(yǔ)句對(duì)性能的影響。一個(gè)很好的模式是將錯(cuò)誤委托給一個(gè)函數(shù)處理,例如:
try{ doSomething(); }catch(ex){ handleError(ex); //委托給處理器方法 }
優(yōu)化后的代碼,handleError方法是catch子句中唯一執(zhí)行的代碼。該函數(shù)接收異常對(duì)象作為參數(shù),這樣你可以更加靈活和統(tǒng)一的處理錯(cuò)誤。由于只執(zhí)行一條語(yǔ)句,且沒(méi)有局部變量的訪問(wèn),作用域鏈的臨時(shí)改變就不會(huì)影響代碼性能了。
以上就是本文的全部?jī)?nèi)容,希望通過(guò)這篇文章大家更加了解javascript的作用域與作用域鏈,大家共同進(jìn)步。
- Javascript變量的作用域和作用域鏈詳解
- JavaScript 作用域鏈解析
- 深入理解JavaScript高級(jí)之詞法作用域和作用域鏈
- JavaScript作用域鏈?zhǔn)褂媒榻B
- 深入Javascript函數(shù)、遞歸與閉包(執(zhí)行環(huán)境、變量對(duì)象與作用域鏈)使用詳解
- 你必須知道的Javascript知識(shí)點(diǎn)之"深入理解作用域鏈"的介紹
- JavaScript中的作用域鏈和閉包
- 深入理解JavaScript系列(14) 作用域鏈介紹(Scope Chain)
- 深入理解JavaScript作用域和作用域鏈
- javascript 嵌套的函數(shù)(作用域鏈)
- javascript作用域鏈(Scope Chain)用法實(shí)例解析
相關(guān)文章
JavaScript中條件語(yǔ)句的優(yōu)化技巧總結(jié)
這篇文章主要給大家介紹了關(guān)于JavaScript中條件語(yǔ)句的優(yōu)化技巧,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12getComputedStyle與currentStyle獲取樣式(style/class)
通過(guò)document.getElementById(element).style.xxx可以獲取元素的樣式信息但是對(duì)于通過(guò)class屬性引用的外部樣式表就獲取不到了,感興趣的朋友可以了解下2013-03-03JS動(dòng)態(tài)添加與刪除select中的Option對(duì)象(示例代碼)
本篇文章主要介紹了JS動(dòng)態(tài)添加與刪除select中的Option對(duì)象示例代碼。需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-12-12JavaScript對(duì)象拷貝與賦值操作實(shí)例分析
這篇文章主要介紹了JavaScript對(duì)象拷貝與賦值操作,結(jié)合實(shí)例形式分析了javascript對(duì)象定義、拷貝、賦值等相關(guān)操作技巧與注意事項(xiàng),需要的朋友可以參考下2018-12-12Taro UI框架開(kāi)發(fā)小程序?qū)崿F(xiàn)左滑喜歡右滑不喜歡效果的示例代碼
這篇文章主要介紹了Taro UI開(kāi)發(fā)小程序?qū)崿F(xiàn)左滑喜歡右滑不喜歡效果,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05JavaScript學(xué)習(xí)筆記之?dāng)?shù)組隨機(jī)排序
這篇文章主要介紹了JavaScript學(xué)習(xí)筆記之?dāng)?shù)組隨機(jī)排序的相關(guān)資料,需要的朋友可以參考下2016-03-03js實(shí)現(xiàn)年月日表單三級(jí)聯(lián)動(dòng)
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)年月日表單三級(jí)聯(lián)動(dòng),生日欄表單三級(jí)聯(lián)動(dòng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03