javascript 範圍鏈
執行環境(Execution Context) 所有的javascript代碼都是在一個執行環境中被執行的。它只是一種機制,用來完成運行時範圍、生存期等方面的處理。 代碼分為三種類型: Global Code Eval Code Function Code 這是一個EC結構 可以理解如下:
activeExecutionContext = { VO: {...}, // or AO this: thisValue, Scope: [ // Scope chain // 所有變數對象的列表 // for identifiers lookup ]};
當一段程式開始時,會先進入到全域執行內容環境。此時如果調用了某些函數,就會進入他們的上下文環境,執行完之後再退出該上下文環境。 假設啟用了一個EC1的上下文,圖解如下 變數對象(VO)和使用中的物件(AO) 變數對象是一個與上下文相關的資料範圍,用於儲存被定義在上下文中的變數和函式宣告(不包括函數運算式)。在global全域上下文中,變數對象即使全域對象自身。 copy一個例子
var foo = 10;function bar() {} // // 函式宣告(function baz() {}); // 函數運算式console.log( this.foo == foo, // true window.bar == bar // true);console.log(baz); // 引用錯誤,baz沒有被定義
全域上下文中的變數對象(VO)會有如下屬性: 在一個函數的上下文中,變數對象被表示為使用中的物件 使用中的物件在進入上下文中初始化成了 AO = { arguments: <ArgO>};arguments屬性值就是Arguments對象 之後就是儲存當前內容相關的變數與函式宣告 範圍鏈 範圍鏈是一個 對象列表,用於檢索上下文中出現的 標識符(變數名稱、函式宣告,普通參數)。 在函數建立的時候,當前的範圍鏈被儲存到了函數的[[scope]]屬性中。當進入上下文建立AO/VO之後,上下文(EC)的Scope屬性作了如下處理: Scope = AO|VO + [[Scope]] 知識點就是這些,現在我們來一段代碼,把過程給串聯起來。在這之前,我們要知道另一個知識,就是執行內容的代碼被分成兩個基本的階段來處理 進入執行內容執行代碼
var x = 10;function foo(m) { var y = 20; function bar() { var z = 30; alert(x + y + z + m); } bar();}foo(10); // 60
首先先進入全域上下文環境 全域內容相關的變數對象(代碼執行時,x才被賦值)是: globalContext.VO === Global = { x: 10 foo: <reference to function>};globalContext.Scope = globalContext.VO; 當"foo"建立時,
foo.[[Scope]] = [ globalContext.Scope];//也就是foo.[[Scope]] = [ globalContext.VO];
在"foo"啟用時,進入了foo的上下文,foo上下文中的使用中的物件(代碼執行時,y才被賦值)是: fooContext.AO = { arguments:<Arg>, m: 10, y: 20, bar: <reference to function>};此時foo上下文中的範圍鏈為: fooContext.Scope = fooContext.AO + foo.[[Scope]] // i.e.: fooContext.Scope = [ fooContext.AO, globalContext.VO];函數"bar"建立時,其[[scope]]為: bar.[[Scope]] = fooContext.Scope;//也就是bar.[[Scope]] = [ fooContext.AO, globalContext.VO];同理,bar就不寫了。最後bar內容相關的範圍鏈為:
barContext.Scope = barContext.AO + bar.[[Scope]] barContext.Scope = [ barContext.AO, fooContext.AO, globalContext.VO];
在alert執行時,範圍鏈中尋找標示符如下 尋找 x, barContext.AO 無 ----> fooContext.AO 無 ---->globalContext.VO 有 尋找 Z, barContext.AO 有 其他的就自己看了。 局部變數、全域變數 在範圍鏈中尋找標識符是需要花時間的,所以就明白為什麼需要盡量使用局部變數(將頻繁使用的全域變數緩衝下來),全域變數盡量少用了。並且with會破壞範圍鏈,它會將指定的VO/AO加入到範圍鏈的頂端,這樣在標識符尋找時,需要尋找更長的範圍鏈。 閉包 理解了上面的概念,閉包就很容易理解了。閉包是代碼塊和建立該代碼塊的上下文中資料的結合。