閉包是JavaScript的復雜特性之一,搞清楚它有益于JS復雜程序的編寫和排錯。什么是閉包呢?讓我們先看看《jQuery基礎教程》一書中對閉包的描述:
“當內部函數在定義它的作用于的外部被引用時,就創建了該內部函數的一個閉包。在這種情況下,我們稱布什內部函數局部變量的變量為自由變量,稱外部函數的調用環境為封閉閉包的環境。從本質上講,如果內部函數引用了位于外部函數中的變量,相當于授權該變量能夠被延遲使用。因此,當外部函數調用完成后,這些變量的內存不會被釋放,因為閉包仍然需要使用它們。”
這段話解釋得很清楚了。如果再簡短一些,用我自己的話說,閉包=封閉+打包,它的作用是將內部函數及其使用到的外部變量(內部函數的作用環境)封閉在一起打包使用。具體過程請看下面的程序:
<SCRIPT LANGUAGE="JavaScript">
<!--
// 定義兩個引用
var pointer1;
var pointer2;
// 外部函數定義
function outer(){
// 外部函數的局部變量
var i=0;
// 在外部函數內部定義的內部函數,使用了外部函數的局部變量i
function inner(){
alert(i);
i++;
}
// 將兩個引用指向內部函數inner
pointer1=inner;
pointer2=inner;
}
outer();
alert("通過引用調用內部函數開始");
pointer1();// 彈出0
pointer1();// 彈出1
pointer2();// 彈出2
pointer2();// 彈出3
//-->
</SCRIPT>
執行以上程序,很容易就發覺,內部函數inner和外部函數定義的局部變量i被封閉并打包在一起了,就是閉包。這種方式和Java中向匿名類傳遞參數需要用final指定外部變量的做法異曲同工,都是改變局部變量的生存周期和作用范圍以配合特定函數的使用而已,不同的是Java在明JS在暗。
如果創建了內部函數的多個副本,那么被打包的外部函數中的變量也會產生互不影響的多個副本,從下面的程序可以清晰的看出來:
<SCRIPT LANGUAGE="JavaScript">
<!--
// 定義兩個引用
var pointer1;
var pointer2;
// 外部函數定義
function outer(){
// 外部函數的局部變量
var i=0;
// 在外部函數內部定義的內部函數,使用了外部函數的局部變量i
function inner(){
alert(i);
i++;
}
// 返回內部函數的引用
return inner;
}
// 將引用指向內部函數
pointer1=outer();
pointer2=outer();
alert("通過引用調用內部函數開始");
pointer1();// 彈出0
pointer1();// 彈出1
pointer2();// 彈出0
pointer2();// 彈出1
//-->
</SCRIPT>
明顯,pointe1和pointer2指向的是inner的兩個副本,它們各自有不同的閉包環境,因此使用到的i也是從屬于各自閉包環境的兩個不同變量,它們地址,值都各不相同,相同的只有名字而已,就好像兩個一般函數中都有變量i一樣。
以上程序的完整代碼請見:
http://www.tkk7.com/Files/heyang/closure20090823214249.rar
JS閉包的這個特性對動態創建的程序特別有效,如下圖中“刪除”按鈕的創建,就會用到閉包。
請看代碼:
// deleteImgCell
var deleteBtn=document.createElement("input");
deleteBtn.setAttribute("type","button");
deleteBtn.setAttribute("value","刪除");
deleteBtn.onclick=function(){deleteRow(code);};// 這里用到了閉包
var deleteBtnCell=document.createElement("td");
deleteBtnCell.appendChild(deleteBtn);
deleteBtn.onclick=function(){deleteRow(code);};這句代碼為什么一定要用一個函數體包容一下呢?我們知道deleteRow這個函數的作用是刪除掉一個表格行,傳入的參數是行的id,而每行的id是不同的,程序中的id值來自于局部變量code,我們不希望它創建完按鈕后值發生變化而是希望它運行時的值和每個按鈕的點擊相應函數綁定在一起,用JS的閉包正好完成了這一點。
以上代碼的完整程序請見:
http://www.tkk7.com/Files/heyang/StockTable.rar
在
http://www.tkk7.com/heyang/archive/2009/08/21/292106.html 中的程序里也使用到了閉包,以下的程序:
window.onload=function(){
var menubar=$("menubar");
for(var i=0;i<menubar.childNodes.length;i++){
new function(){// 這里使用了閉包,將局部變量和事件響應函數打包封閉在了一起.
var li=menubar.childNodes[i];
var subul=li.childNodes[2];
li.attachEvent('onmouseover',
function(){
subul.style.display="block";
}
);
li.attachEvent('onmouseout',
function(){
subul.style.display="none";
}
);
}
}
}
當然,不要紅字部分的function也是一種閉包,只是打包封閉的環境錯誤了而已.
以上代碼的完整程序請見:
http://www.tkk7.com/Files/heyang/JSCSSPopupmenu20090821151741.rar