深入理解JavaScript閉包概念__Java
來源:互聯網
上載者:User
閉包向來給包括JavaScript程式員在內的程式員以神秘,高深的感覺,事實上,閉包的概念在函數式程式設計語言中算不上是難以理解的知識。如果對範圍,函數為獨立的對象這樣的基本概念理解較好的話,理解閉包的概念並在實際的編程實踐中應用則頗有水到渠成之感。
在DOM的事件處理方面,大多數程式員甚至自己已經在使用閉包了而不自知,在這種情況下,對於瀏覽器中內嵌的JavaScript引擎的bug可能造成記憶體流失這一問題姑且不論,就是程式員自己調試也常常會一頭霧水。
用簡單的語句來描述JavaScript中的閉包的概念:由於JavaScript中,函數是對象,對象是屬性的集合,而屬性的值又可以是對象,則在函數內定義函數成為理所當然,如果在函數func內部聲明函數inner,然後在函數外部調用inner,這個過程即產生了一個閉包。
閉包的特性
我們先來看一個例子,如果不瞭解JavaScript的特性,很難找到原因:
var outter = [];
function clouseTest () {
var array = ["one", "two", "three", "four"];
for(var i = 0; i < array.length;i++){
var x = {};
x.no = i;
x.text = array[i];
x.invoke = function(){
print(i);
}
outter.push(x);
}
}
//調用這個函數
clouseTest();
print(outter[0].invoke());
print(outter[1].invoke());
print(outter[2].invoke());
print(outter[3].invoke());
啟動並執行結果如何呢。很多初學者可能會得出這樣的答案:
0
1
2
3
然而,運行這個程式,得到的結果為:
4
4
4
4
其實,在每次迭代的時候,這樣的語句x.invoke = function(){print(i);}並沒有被執行,只是構建了一個函數體為”print(i);”的函數對象,如此而已。而當i=4時,迭代停止,外部函數返回,當再去調用outter[0].invoke()時,i的值依舊為4,因此outter數組中的每一個元素的invoke都返回i的值:4。
如何解決這一問題呢。我們可以聲明一個匿名函數,並立即執行它:
var outter = [];
function clouseTest2(){
var array = ["one", "two", "three", "four"];
for(var i = 0; i < array.length;i++){
var x = {};
x.no = i;
x.text = array[i];
x.invoke = function(no){
return function(){
print(no);
}
}(i);
outter.push(x);
}
}
clouseTest2();
這個例子中,我們為x.invoke賦值的時候,先運行一個可以返回一個函數的函數,然後立即執行之,這樣,x.invoke的每一次迭代器時相當與執行這樣的語句:
//x == 0
x.invoke = function(){print(0);}
//x == 1
x.invoke = function(){print(1);}
//x == 2
x.invoke = function(){print(2);}
//x == 3
x.invoke = function(){print(3);}
這樣就可以得到正確結果了。閉包允許你引用存在於外部函數中的變數。然而,它並不是使用該變數建立時的值,相反,它使用外部函數中該變數最後的值。
閉包的用途
現在,閉包的概念已經清晰了,我們來看看閉包的用途。事實上,通過使用閉包,我們可以做很多事情。比如類比物件導向的代碼風格;更優雅,更簡潔的表達出代碼;在某些方面提升代碼的執行效率。
匿名自執行函數
上一節中的例子,事實上就是閉包的一種用途,根據前面講到的內容可知,所有的變數,如果不加上var關鍵字,則預設的會添加到全域對象的屬性上去,這樣的臨時變數加入全域對象有很多壞處,比如:別的函數可能誤用這些變數;造成全域對象過於龐大,影響訪問速度(因為變數的取值是需要從原型鏈上遍曆的)。除了每次使用變數都是用var關鍵字外,我們在實際情況下經常遇到這樣一種情況,即有的函數只需要執行一次,其內部變數無需維護,比如UI的初始化,那麼我們可以使用閉包:
var datamodel = {
table : [],
tree : {}
};
(function(dm){
for(var i = 0; i < dm.table.rows; i++){
var row = dm.table.rows[i];
for(var j = 0; j < row.cells; i++){
drawCell(i, j);