摘要:每一個(gè)運(yùn)行期上下文都和一個(gè)作用域鏈關(guān)聯(lián)。這個(gè)對(duì)象將被推入作用域鏈的頭部,這意味著函數(shù)的所有局部變量現(xiàn)在處于第二個(gè)作用域鏈對(duì)象中,因此訪問代價(jià)更高了。在代碼塊內(nèi)部,函數(shù)的所有局部變量將會(huì)被放在第二個(gè)作用域鏈對(duì)象中。
參考:
Javascript作用域原理
理解 JavaScript 作用域和作用域鏈
JavaScript 作用域作用域就是變量與函數(shù)的可訪問范圍,即作用域控制著變量與函數(shù)的可見性和生命周期。
在JavaScript中,變量的作用域有 全局作用域和 局部作用域兩種。
全局作用域(Global Scope)在 代碼中任何地方都能訪問到的對(duì)象擁有全局作用域,一般來說以下幾種情形擁有全局作用域:
(1)最外層函數(shù)和在最外層函數(shù)外面定義的變量擁有全局作用域,例如:
var authorName="山邊小溪"; function doSomething(){ var blogName="夢(mèng)想天空"; function innerSay(){ alert(blogName); } innerSay(); } alert(authorName); //山邊小溪 alert(blogName); //腳本錯(cuò)誤 doSomething(); //夢(mèng)想天空 innerSay() //腳本錯(cuò)誤
(2)所有末定義直接賦值的變量自動(dòng)聲明為擁有全局作用域,例如:
function doSomething(){ var authorName="山邊小溪"; blogName="夢(mèng)想天空"; alert(authorName); } doSomething(); //山邊小溪 alert(blogName); //夢(mèng)想天空 alert(authorName); //腳本錯(cuò)誤
變量blogName擁有全局作用域,而authorName在函數(shù)外部無法訪問到。
局部作用域(Local Scope)和全局作用域相反,局部作用域一般只在固定的代碼片段內(nèi)可訪問到,最常見的例如函數(shù)內(nèi)部,所有在一些地方也會(huì)看到有人把這種作用域稱為 函數(shù)作用域,例如下列代碼中的blogName和函數(shù)innerSay都只擁有局部作用域。
function doSomething(){ var blogName="夢(mèng)想天空"; function innerSay(){ alert(blogName); } innerSay(); } alert(blogName); //腳本錯(cuò)誤 innerSay(); //腳本錯(cuò)誤JavaScript 的作用域鏈(Scope Chain) [[scope]] 屬性
函數(shù)對(duì)象其中一個(gè)內(nèi)部屬性是[[Scope]],由ECMA-262標(biāo)準(zhǔn)第三版定義,該內(nèi)部屬性包含了 函數(shù)被創(chuàng)建的作用域中對(duì)象的集合,這個(gè)集合被稱為函數(shù)的 作用域鏈,它決定了哪些數(shù)據(jù)能被函數(shù)訪問。
請(qǐng)看例子:
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í)行過程中,每遇到一個(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ù)運(yùn)行在它們被定義的作用域里JS權(quán)威指南 中有一句很精辟的描述:
JavaScript中的函數(shù)運(yùn)行在它們被定義的作用域里,而不是它們被執(zhí)行的作用域里.
在JS中,作用域的概念和其他語(yǔ)言差不多, 在每次調(diào)用一個(gè)函數(shù)的時(shí)候 ,就會(huì)進(jìn)入一個(gè)函數(shù)內(nèi)的作用域,當(dāng)從函數(shù)返回以后,就返回調(diào)用前的作用域.
JS的作用域的實(shí)現(xiàn)具體過程如下(ECMA262中所述):
任何執(zhí)行上下文時(shí)刻的作用域, 都是由作用域鏈(scope chain, 后面介紹)來實(shí)現(xiàn).
在一個(gè)函數(shù)被定義的時(shí)候, 會(huì)將它定義時(shí)刻的scope chain鏈接到這個(gè)函數(shù)對(duì)象的[[scope]]屬性.
在一個(gè)函數(shù)對(duì)象被調(diào)用的時(shí)候,會(huì)創(chuàng)建一個(gè)活動(dòng)對(duì)象(也就是一個(gè)對(duì)象), 然后對(duì)于每一個(gè)函數(shù)的形參,都命名為該活動(dòng)對(duì)象的命名屬性, 然后將這個(gè)活動(dòng)對(duì)象做為此時(shí)的作用域鏈(scope chain)最前端, 并將這個(gè)函數(shù)對(duì)象的[[scope]]加入到scope chain中.
看個(gè)例子:
函數(shù)對(duì)象的[[scope]]屬性是在定義一個(gè)函數(shù)的時(shí)候決定的, 而非調(diào)用的時(shí)候, 所以如下面的例子:
var name = "laruence"; function echo() { alert(name); } function env() { var name = "eve"; echo();markdown previewmarkdown previewmarkdown previewmarkdown preview } env(); // 運(yùn)行結(jié)果是: laruence
結(jié)合上面的知識(shí), 我們來看看下面這個(gè)例子:
function factory() { var name = "laruence"; var intro = function(){ alert("I am " + name); } return intro; } function app(para){ var name = para; var func = factory(); func(); } app("eve");
當(dāng)調(diào)用app的時(shí)候, scope chain是由: {window活動(dòng)對(duì)象(全局)}->{app的活動(dòng)對(duì)象} 組成.
在剛進(jìn)入app函數(shù)體時(shí), app的活動(dòng)對(duì)象有一個(gè)arguments屬性, 倆個(gè)值為undefined的屬性: name和func. 和一個(gè)值為’eve’的屬性para;
此時(shí)的scope chain如下:
[[scope chain]] = [ { para : "eve", name : undefined, func : undefined, arguments : [] }, { window call object } ]
當(dāng)調(diào)用進(jìn)入factory的函數(shù)體的時(shí)候, 此時(shí)的factory的scope chain為:
[[scope chain]] = [ { name : undefined, intor : undefined }, { window call object } ]
注意到, 此時(shí)的作用域鏈中, 并不包含app的活動(dòng)對(duì)象.
在定義intro函數(shù)的時(shí)候, intro函數(shù)的[[scope]]為:
[[scope chain]] = [ { name : "laruence", intor : undefined }, { window call object } ]
從factory函數(shù)返回以后,在app體內(nèi)調(diào)用intor的時(shí)候, 發(fā)生了標(biāo)識(shí)符解析, 而此時(shí)的sope chain是:
[[scope chain]] = [ { intro call object }, { name : "laruence", intor : undefined }, { window call object } ]
因?yàn)?b>scope chain中,并不包含factory活動(dòng)對(duì)象. 所以, name標(biāo)識(shí)符解析的結(jié)果應(yīng)該是factory活動(dòng)對(duì)象中的name屬性, 也就是’laruence’.
所以運(yùn)行結(jié)果是:
I am laruence作用域鏈和代碼優(yōu)化
從作用域鏈的結(jié)構(gòu)可以看出,在運(yùn)行期上下文的作用域鏈中,標(biāo)識(shí)符所在的位置越深,讀寫速度就會(huì)越慢。
全局變量總是存在于運(yùn)行期上下文作用域鏈的最末端,因此在標(biāo)識(shí)符解析的時(shí)候,查找全局變量是最慢的。
所以,在編寫代碼的時(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ì)象中才能找到。
這段代碼可以重寫如下:
function changeColor(){ var doc=document; doc.getElementById("btnChange").onclick=function(){ doc.getElementById("targetCanvas").style.backgroundColor="red"; }; }
這段代碼比較簡(jiǎn)單,重寫后不會(huì)顯示出巨大的性能提升,但是如果程序中有大量的全局變量被從反復(fù)訪問,那么重寫后的代碼性能會(huì)有顯著改善。
改變作用域鏈函數(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 語(yǔ)句和 catch 語(yǔ)句影響。
with 語(yǔ)句with語(yǔ)句是對(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(); }; } }
這里使用with語(yǔ)句來避免多次書寫document,看上去更高效,實(shí)際上產(chǎn)生了性能問題。
當(dāng)代碼運(yùn)行到with語(yǔ)句時(shí),運(yùn)行期上下文的作用域鏈臨時(shí)被改變了。
一個(gè)新的可變對(duì)象被創(chuàng)建,它包含了參數(shù)指定的對(duì)象的所有屬性。
這個(gè)對(duì)象將被推入作用域鏈的頭部,這意味著函數(shù)的所有局部變量現(xiàn)在處于第二個(gè)作用域鏈對(duì)象中,因此訪問代價(jià)更高了。
如下圖所示:
因此在程序中應(yīng)避免使用with語(yǔ)句,在這個(gè)例子中,只要簡(jiǎn)單的把document存儲(chǔ)在一個(gè)局部變量中就可以提升性能。
catch語(yǔ)句另外一個(gè)會(huì)改變作用域鏈的是try-catch語(yǔ)句中的catch語(yǔ)句。
當(dāng)try代碼塊中發(fā)生錯(cuò)誤時(shí),執(zhí)行過程會(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)試和異常處理中非常有用,因此不建議完全避免。
你可以通過優(yōu)化代碼來減少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ǔ)句,且沒有局部變量的訪問,作用域鏈的臨時(shí)改變就不會(huì)影響代碼性能了。
Javascript 的預(yù)編譯在JS中, 是有預(yù)編譯的過程的, JS在執(zhí)行每一段JS代碼之前, 都會(huì)首先處理var關(guān)鍵字和function定義式(函數(shù)定義式和函數(shù)表達(dá)式).
如上文所說, 在調(diào)用函數(shù)執(zhí)行之前, 會(huì)首先創(chuàng)建一個(gè)活動(dòng)對(duì)象, 然后搜尋這個(gè)函數(shù)中的局部變量定義,和函數(shù)定義, 將變量名和函數(shù)名都做為這個(gè)活動(dòng)對(duì)象的同名屬性, 對(duì)于局部變量定義,變量的值會(huì)在真正執(zhí)行的時(shí)候才計(jì)算, 此時(shí)只是簡(jiǎn)單的賦為undefined.
而對(duì)于函數(shù)的定義,是一個(gè)要注意的地方:
這就是函數(shù)定義式和函數(shù)表達(dá)式的不同, 對(duì)于函數(shù)定義式, 會(huì)將函數(shù)定義提前. 而函數(shù)表達(dá)式, 會(huì)在執(zhí)行過程中才計(jì)算.
var name = "laruence"; age = 26;
我們都知道不使用var關(guān)鍵字定義的變量, 相當(dāng)于是全局變量, 聯(lián)系到我們剛才的知識(shí):
在對(duì)age做標(biāo)識(shí)符解析的時(shí)候, 因?yàn)槭菍懖僮? 所以當(dāng)找到到全局的window活動(dòng)對(duì)象的時(shí)候都沒有找到這個(gè)標(biāo)識(shí)符的時(shí)候, 會(huì)在window活動(dòng)對(duì)象的基礎(chǔ)上, 返回一個(gè)值為undefined的age屬性.
現(xiàn)在, 也許你注意到了我剛才說的: JS在執(zhí)行每一段JS代碼.
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/78873.html
摘要:關(guān)于作用域?qū)崿F(xiàn)的描述任何執(zhí)行上下文時(shí)刻的作用域,都是由作用域鏈來實(shí)現(xiàn)的。在一個(gè)函數(shù)被定義的時(shí)候,會(huì)將它此時(shí)的作用域鏈鏈接到這個(gè)函數(shù)對(duì)象的屬性。參考資料鳥哥作用域原理理解作用域和作用域鏈阮一峰老師微博上的關(guān)于作用域的一道題 javascript作用域原理學(xué)習(xí) 在每次調(diào)用一個(gè)函數(shù)的時(shí)候,就會(huì)進(jìn)入一個(gè)函數(shù)內(nèi)的作用域,當(dāng)從函數(shù)返回 以后,就會(huì)返回調(diào)用前的作用域。 ECMA262關(guān)于作...
摘要:函數(shù)的作用域也可被分為全局作用域和局部作用域函數(shù)作用域被定義在指定函數(shù)內(nèi)部的函數(shù)被稱為局部函數(shù)或內(nèi)部函數(shù)。局部變量在函數(shù)內(nèi)部聲明的變量被成為局部變量,它只能在函數(shù)的內(nèi)部進(jìn)行訪問。 作用域 概述 變量和函數(shù)都具有作用域 作用域就是變量和函數(shù)的可被訪問的范圍 控制著變量和函數(shù)的可見性和生命周期。變量的作用域可被分為全局作用域和局部作用域(函數(shù)作用域) 如果變量是被定義在全局作用域的話 在J...
摘要:閉包是怎么通過作用域鏈霸占更多內(nèi)存的本文是作者學(xué)習(xí)高級(jí)程序設(shè)計(jì)第一小節(jié)的一點(diǎn)個(gè)人理解,詳細(xì)教程請(qǐng)參考原教材。函數(shù)執(zhí)行過程創(chuàng)建了一個(gè)函數(shù)的活動(dòng)對(duì)象,作用域鏈的最前端指向這個(gè)對(duì)象。函數(shù)執(zhí)行完畢返回值后執(zhí)行環(huán)境作用域鏈和活動(dòng)對(duì)象一并銷毀。 JavaScript 閉包是怎么通過作用域鏈霸占更多內(nèi)存的? 本文是作者學(xué)習(xí)《JavaScript 高級(jí)程序設(shè)計(jì)》7.2第一小節(jié)的一點(diǎn)個(gè)人理解,詳細(xì)教程請(qǐng)...
摘要:一前言這個(gè)周末,注意力都在學(xué)習(xí)基礎(chǔ)知識(shí)上面,剛好看到了閉包這個(gè)神圣的東西,所以打算把這兩天學(xué)到的總結(jié)下來,算是鞏固自己所學(xué)。因此要注意閉包的使用,否則會(huì)導(dǎo)致性能問題。五總結(jié)閉包的作用能夠讀取其他函數(shù)內(nèi)部變量。 # 一、前言 這個(gè)周末,注意力都在學(xué)習(xí)基礎(chǔ)Js知識(shí)上面,剛好看到了閉包這個(gè)神圣的東西,所以打算把這兩天學(xué)到的總結(jié)下來,算是鞏固自己所學(xué)。也可能有些不正確的地方,也請(qǐng)大家看到了,麻...
摘要:全局執(zhí)行環(huán)境的變量對(duì)象始終是作用域鏈中的最后一個(gè)變量對(duì)象。綜上,每個(gè)函數(shù)對(duì)應(yīng)一個(gè)執(zhí)行環(huán)境,每個(gè)執(zhí)行環(huán)境對(duì)應(yīng)一個(gè)變量對(duì)象,而多個(gè)變量對(duì)象構(gòu)成了作用域鏈,如果當(dāng)前執(zhí)行環(huán)境是函數(shù),那么其活動(dòng)對(duì)象在作用域鏈的前端。 1.幾個(gè)概念 先說幾個(gè)概念:函數(shù)、執(zhí)行環(huán)境、變量對(duì)象、作用域鏈、活動(dòng)對(duì)象。這幾個(gè)東東之間有什么關(guān)系呢,往下看~ 函數(shù) 函數(shù)大家都知道,我想說的是,js中,在函數(shù)內(nèi)部有兩個(gè)特殊...
摘要:我們?cè)賮砜匆幌碌谝欢未a小紅小黑腳本出錯(cuò)腳本出錯(cuò)在這段代碼中變量與函數(shù),都擁有局部作用域。作用域鏈的最前端,始終都是當(dāng)前執(zhí)行代碼所在的作用域的變量對(duì)象。 個(gè)人博客原址 無論什么語(yǔ)言中,作用域都是一個(gè)十分重要的概念,在JavaScript中也不例外,作用域定義了變量或者函數(shù)有權(quán)訪問的范圍,決定了它們各自的行為。要理解JavaScript中的作用域首先就要知道:在let出現(xiàn)之前,JS中變...
閱讀 1458·2021-09-13 10:25
閱讀 629·2019-08-30 15:53
閱讀 2322·2019-08-30 15:44
閱讀 2105·2019-08-29 17:20
閱讀 1652·2019-08-29 16:36
閱讀 1863·2019-08-29 14:10
閱讀 1844·2019-08-29 12:44
閱讀 1229·2019-08-23 14:13