讀古今文學網 > 編寫高質量代碼:改善JavaScript程序的188個建議 > 建議61:使用閉包跨域開發 >

建議61:使用閉包跨域開發

閉包是指詞法表示包括不必計算的變量的函數,閉包涵數能夠使用函數外定義的變量。閉包結構有以下兩個比較鮮明的特性。

(1)封閉性

外界無法訪問閉包內部的數據,如果在閉包內聲明變量,外界是無法訪問的,除非閉包主動向外界提供訪問接口。

(2)持久性

對於一般函數來說,在調用完畢之後,系統會自動註銷函數,而對於閉包來說,在外部函數被調用之後,閉包結構依然保存在系統中,閉包中的數據依然存在,從而實現對數據的持久使用。例如:


function f(x){

var a=x;

var b=function{

return x;

}

return b;

}

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 a=1;

var b=function{

return a;

}

a++;

return b;

}

var c=f;

alert(c);//返回2,而不是1


在上面示例中,閉包中的變量a存儲的值並不是對上面行變量a的值的簡單複製,而是繼續引用外部函數定義的局部變量a中的值,直到外部函數f調用返回。閉包不會因為外部函數環境的註銷而消失,會始終存在。例如:


<script language=\"javascript\"type=\"text/javascript\">

function f{

var a=1;

b=function{

alert(a);

}

c=function{

a++;

}

d=function(x){

a=x;

}

}

</script>

<button onclick=\"f\">按鈕1:(f)</button><br/>

<button onclick=\"b\">按鈕2:(b=function{alert(a);})</button><br/>

<button onclick=\"c\">按鈕3:(c=function{a++;})</button><br/>

<button onclick=\"d(100)\">按鈕4:(d=function(x){a=x;})(100)</button><br/>


在上面示例中,在函數f中定義了3個閉包涵數,它們分別指向並寄存局部變量a的值,並根據不同的操作動態跟蹤變量a的值。當在瀏覽器中預覽時,首先應該單擊「按鈕1」,調用函數f,生成3個閉包,3個閉包同時指向局部變量a的引用,因此,當函數f返回時,這3個閉包涵數都沒有被註銷,變量a由於被閉包引用而繼續存在。這時,如果直接單擊「按鈕2」和「按鈕4」,那麼會由於沒有在系統中生成閉包結構,而彈出編譯錯誤。單擊「按鈕3」將動態遞增變量a的值,此時如果單擊「按鈕2」,則會彈出提示值2。如果單擊「按鈕4」,則向變量a傳遞值100,將動態改變閉包中寄存的值,此時如果單擊「按鈕2」,則會彈出提示值100。