標籤:
要理解javascript函數的定義與執行,首先需要知道這幾個重要的概念,現在可以Crowdsourced Security Testing道稍後再理解!
函數的執行環境(excution context)、使用中的物件(call object)、範圍(scope)、範圍鏈(scope chain)。
接下來,我們以這個函數為例進行分析: 步驟:
1、設定範圍鏈當定義函數a的時候,JS解譯器會將函數a的
範圍鏈(scope chain)設定為
“定義a時a所在的環境”,此處a第一個添加的範圍是window對象。(如果a是一個全域函數,則scope chain中只有window對象。)
個人見解:範圍鏈裡面其實是包含的使用中的物件,使用中的物件可以理解為是用來識別範圍的。(就像是一個商場分為A、B、C 三個區,就可以理解為在這個商場的範圍鏈裡面,有A、B、C 3個使用中的物件,3個範圍!(“個人見解,可能不恰當”)。
2、執行環境當執行函數a的時候,a會進入相應的執行環境(excution context)。
個人見解:建立執行環境分為
建立範圍和
建立使用中的物件兩步。
3、範圍當建立執行環境的過程中,首先會為a 添加一個scope屬性,即a的範圍,其值就是第一步中的scope chain。即a.scope = a的範圍鏈。
個人見解:可以把範圍和範圍鏈理解成是名字不同但作用相同!
4、建立使用中的物件建立完範圍,緊接著執行環境會
建立一個使用中的物件(call object)。使用中的物件也是一個擁有屬性的對象,但它不具有原型而且不能通過javascript代碼直接存取(可以看下面的圖或者個人見解理解)。
建立完使用中的物件後,把使用中的物件添加到a的範圍鏈的最頂端。此時a的範圍鏈包含兩個對象:a的使用中的物件和window對象。下一步是在使用中的物件上添加一個arguments屬性,它儲存著調用函數a時所傳遞的參數。最後所有的函數a的形參和內部的函數b的引用也被添加到a的使用中的物件上。在這一步中,完成了函數b的定義,因此如同第3步,函數b的範圍被設定為b所定義的環境,即a的範圍。(就類似於a的範圍鏈中第一個加入的範圍是window對象。)!
個人見解:(1) 使用中的物件是一個為了理解而添加的名詞,實際不存在,所以不具有原型、也不能用實際代碼訪問。(類似於磁感線,只是為了描述)
完結:到此,整個函數a從定義到執行的步驟就完成了。此時a返回b的引用給c,又因為函數b的範圍鏈包含了函數a的使用中的物件的引用,也就是說b中可以訪問到a中定義的所有變數和函數。又函數b被c引用,函數b依賴於a,因此函數a再返回後不會被GC回收(參考最下方javascript的記憶體回收機制)! 當函數b被執行的時候也會像以上步驟一樣。因此執行時b的範圍鏈包含了3個對象:b的使用中的物件,a的使用中的物件,window對象,: ,當在函數b訪問一個變數的時候,搜尋順序是:b的使用中的物件 —>b的原型對象(存在的話)—>a的使用中的物件 —>window對象
變數尋找機制:先尋找自身的使用中的物件,如果存在則返回,如果不存在且該函數存在prototype原型對象,則尋找原型對象。依次尋找a的使用中的物件、window對象。直到找到為止,如果整個範圍鏈上沒有找到,則返回undefined。 總結:以上提到了兩個重要的詞語:函數的定義與執行。文中提到
函數的範圍是在定義函數的時候就已經確定,而不是在執行的時候確定(參看步驟1和3).用一段代碼來說明這個問題:
1 function f(x){2 var g = function(){ return x;}3 return g;4 }5 var h = f(1);6 alert(h());這段代碼中變數h指向了f中的那個匿名函數(由g返回,也可以理解為不匿名,因為有名字g(不正規理解))。(1)h的範圍在定義的時候確定:個人理解h的範圍鏈:g的使用中的物件->f的使用中的物件->window對象(因為此處h代表的是返回的"g()"函數,既然是定義為準,就應該是定義"g()"函數時確定,所以 h.scope chain = g.scope chain )網上參考h的範圍鏈:h的使用中的物件 ->f的使用中的物件->window對象(2)h的範圍在執行(alert( h ( ) )的時候確定:h的範圍鏈:h的使用中的物件->alert的使用中的物件->window對象。 結果:(1)應該輸出1 (2)應該輸出undefined 。實踐證明函數的範圍是在定義這個函數的時候就已經確定了!
補充:Javascript的記憶體回收機制
在Javascript中,如果一個對象不再被引用,那麼這個對象就會被GC回收。如果兩個對象互相引用,而不再被第3者所引用,那麼這兩個互相引用的對象也會被回收。因為函數a被b引用,b又被a外的c引用,這就是為什麼函數a執行後不會被回收的原因。
function a(){
var i = 0;
var b = function(){ alert(++i);}
return b;
}
var c = a();
c();
以上代碼如果沒有return b; 當函數a執行完後就會被回收!兩個對象互相引用(a引用b,b引用a),而不再被第3者所引用(沒有返回資料給c),那麼這兩個互相引用的對象也會被回收。
參考文檔:http://www.jb51.net/article/24101.htm (javascript深入理解js閉包)
javascript函數的定義與執行