以前做用域鏈在我眼裏也只是在調用一個對象時一層一層向上找到本身所需的變量或是函數,若沒有則返回undefined,其實大體上說倒是這樣的,可是我須要的是不斷的深刻。javascript
在深刻理解以前先記住兩句話html
1.js中一切皆對象,函數也是對象java
2.函數運行在他們被定義的做用域內,而不是被執行的做用域內。緩存
當定義函數時,會包含[[scope]]屬性(由於js中一切皆對象,函數也是對象),此屬性是函數內部屬性,只容許js引擎訪問,[[scope]]指向做用域鏈(scope lain),而此時僅包含函數
全部全局變量。性能
當調用函數時,會建立"運行期上下文"內部對象,此對象包含本身的做用域鏈,用於解析標識符,並初始化爲調用函數的內部對象[[scope]](即全局變量),這些值按照出現的位置優化
依次複製到運行期上下文中,一塊兒組成"活動對象",並添加至做用域鏈的首位。活動對象包括這次調用函數內部的全部局部變量,形參,命名參數以及this。當運行期函數銷燬時,this
活動對象也隨之銷燬。spa
說了那麼多,確定已經懵了,來個例子消化一下。3d
function add(num1,num2) { var sum = num1 + num2; return sum; }
當定義add函數時,建立[[scope]]屬性,指向做用域鏈(scope chain),僅包含全局對象(global object)以下圖:
在調用add函數時,建立「執行上下文」(execution context)並建立活動對象(activation object),更新做用域鏈,以下圖:
在調用函數每遇到一個變量時,都會經歷標識符解析從而知道從哪裏獲取數據並存儲數據,查找是否有同名標識符,若沒有則查找下一個對象,直到全局變量,若一直沒有找到,則證實此標識符並無被定義。
那麼,來個小問題,爲何編寫js代碼時儘可能避免使用全局變量?
答案:由於使用全局變量時,在做用域鏈中需查找到最後一層,影響性能。
根據上面的所講的以及文章開始時的第二個注意問題,再舉個例子
function output() { var name = "nana"; var intro = function() { alert(name); }; return intro; } function a(para) { var name = para; var func = output(); func(); } a('lili');
在調用函數a時做用域鏈爲:
[[scope chain]] = {
pare: 'lili',
name: undefined,
func: undefined,
arguments: []
},{
window call object
}
在調用函數output時做用域鏈爲:(注意:並不包含a的活動對象)
[[scope chain]] = [{
name: undefined,
intro: undefined
},{
window call object
}]
在定義intro函數時做用域鏈爲
[[scope chain]] = [{
name: 'nana',
intro: undefined
},{
window call object
}]
當從output返回,在a函數中調用intro時,發生標誌符解析,此時做用域鏈爲:
[[scope chain]] = [{
intro call object
},{
name: 'nana',
intro: 'undefined'
},{
window call object
}]根據文章開始的第二句話,函數運行在被定義的做用域內,而不是被執行的做用域內。如上intro函數運行在被定義的做用域內。所以,最後答案是nana。
改變做用域鏈
1>with
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能夠改變做用域,看似很方便,實際上卻影響性能。當代碼讀到with時,做用域鏈又被改爲下圖所示,因此全部函數內部的局部變量都須要查找第二次纔可被訪問,影響了性能,避免使用。
2>try-catch
當try代碼塊發生異常時,則執行catch語句,此時做用域也會像上面那樣臨時被改動,那麼,局部變量也是隻能在第二次查找時纔可匹配到,影響性能,但在調試時try-catch有很大幫助,因此沒必要徹底避免。注意:在執行完catch語句時,做用域鏈又恢復回來。
優化:
1>儘可能避免使用全局變量
2>緩存變量,將使用一次以上的全局變量緩存給一個局部變量。
預編譯
每次說到做用域鏈的時候不得不提一下預編譯
在每次預編譯時,先找var關鍵字,再找function定義式,
看個例子吧!
console.log(a); var a = 10; console.log(a); console.log(typeof func); console.log(typeof d); function func() {} var d = function(){}; console.log(typeof d);
猜猜會輸出什麼呢?
答案是:undefined 10 function undefined function
你猜對了麼?
首先來講變量,在預編譯時找到var關鍵字並簡單賦值爲undefined,只有在執行時,才被賦值。var a = 10,至關於var a; a = 10; 所以,第一個輸出undefined,第二個a已經執行了,因此輸出10。
再來講函數,在預編譯時找到函數定義式,而函數表達式只會在執行過程當中才被執行,所以,第三個打印出function,第四個打印undefined,第五個打印function。
參考資料:
http://naotu.baidu.com/viewshare.html?shareId=av8cg0e3dckw&qq-pf-to=pcqq.group
http://www.cnblogs.com/lhb25/archive/2011/09/06/javascript-scope-chain.html