建議61:使用閉包跨域開發
閉包是指詞法表示包括不必計算的變量的函數,閉包函數能夠使用函數外定義的變量。閉包結構有以下兩個比較鮮明的特性。
(1)封閉性
外界無法通路閉包内部的資料,如果在閉包内聲明變量,外界是無法通路的,除非閉包主動向外界提供通路接口。
(2)持久性
對于一般函數來說,在調用完畢之後,系統會自動登出函數,而對于閉包來說,在外部函數被調用之後,閉包結構依然儲存在系統中,閉包中的資料依然存在,進而實作對資料的持久使用。例如:
function f( x ){
}
var c = f( 1 );
alert(c()); //1。調用閉包函數
在上面示例中,首先在函數f結構體内定義兩個變量,分别存儲參數和閉包結構,而閉包結構中寄存着參數值。當調用函數f之後,函數結構被登出,它的局部變量也随之被登出,是以變量a中存儲的參數值也随之丢失。但由于變量b存儲着閉包結構,是以閉包結構内部的參數值并沒有被釋放,在調用函數之後,依然能夠從閉包結構中讀取到參數值。
從結構上分析,閉包函數與普通函數沒有什麼不同,主要包含以下類型的辨別符:
函數參數(形參變量)。
arguments屬性。
局部變量。
内部函數名。
this(指代閉包函數自身)。
其中this和arguments是系統預設的函數辨別符,不需要特别聲明。這些辨別符在閉包體内的優先級是(其中左側優先級大于右側):this → 局部變量 → 形參 → arguments → 函數名。
下面以一個經典的閉包示例來示範上述抽象描述:
1 function f(x){ // 外部函數
2 var a = x; // 外部函數的局部變量,并把參數值傳遞給它
3 var b = function(){ // 内部函數
4 return a; // 通路外部函數中的局部變量
5 };
6 a++; // 通路後,動态更新外部函數的變量
7 return b; // 内部函數
8 }
9 var c = f(5); // 調用外部函數,并指派
10 alert(c()); // 調用内部函數,傳回外部函數更新後的值6
示範步驟說明如下:
第1步,程式預編譯之後,從第9行開始解析執行,建立上下文環境,建立調用對象,把參數、局部變量、内部的函數轉換為對象屬性。
第2步,執行函數體内代碼。在第6行執行局部變量a的遞加運算,并把這個值傳遞給對象屬性a,内部函數動态保持與局部變量a的聯系,同時更新自己内部調用變量的值。
第3步,外部函數把内部函數傳回給全局變量c,實作内部函數的定義,此時c完全繼承了内部函數的所有結構和資料。
第4步,外部函數傳回後(即傳回值後調用完畢)會自動銷毀,内部的結構、辨別符和資料也随之丢失。
第5步,執行第10行代碼指令,調用内部函數,此時傳回的是外部函數傳回時(銷毀之前)儲存的變量a所存儲的最新資料值,即傳回6。
如果沒有閉包函數的作用,那麼這種資料寄存和傳遞就無法得以實施。例如:
1 function f(x){
2 var a = x;
3 var b = a; // 直接把局部變量的值傳遞給局部變量b
4 a++
5 return b; //局部變量b
6 }
7 var c = f(5);
8 alert(c); //值為5
通過上面的示例可以很直覺地看到,在沒有閉包函數的輔助下,第8行代碼執行後傳回值并沒有與外部函數的局部變量a最後更新的值保持一緻。
閉包在程式開發中具有重要的價值。例如,使用閉包結構能夠跟蹤動态環境中資料的實時變化,并即時存儲。
function f(){
var c = f();
alert(c()); //傳回2,而不是1
在上面示例中,閉包中的變量a存儲的值并不是對上面行變量a的值的簡單複制,而是繼續引用外部函數定義的局部變量a中的值,直到外部函數f調用傳回。閉包不會因為外部函數環境的登出而消失,會始終存在。例如:
按鈕1:(f( ))()
按鈕2:(b = function(){alert( a );})()
按鈕3:(c = function(){a ++ ;})()
按鈕4:(d = function( x ){a = x; }) (100)
在上面示例中,在函數f中定義了3個閉包函數,它們分别指向并寄存局部變量a的值,并根據不同的操作動态跟蹤變量a的值。當在浏覽器中預覽時,首先應該單擊“按鈕1”,調用函數f,生成3個閉包,3個閉包同時指向局部變量a的引用,是以,當函數f傳回時,這3個閉包函數都沒有被登出,變量a由于被閉包引用而繼續存在。這時,如果直接單擊“按鈕2”和“按鈕4”,那麼會由于沒有在系統中生成閉包結構,而彈出編譯錯誤。單擊“按鈕3”将動态遞增變量a的值,此時如果單擊“按鈕2”,則會彈出提示值2。如果單擊“按鈕4”,則向變量a傳遞值100,将動态改變閉包中寄存的值,此時如果單擊“按鈕2”,則會彈出提示值100。