每次被說起閉包的時候,小碼哥老是腦殼犯暈,不知如何做答!javascript
下面是小碼哥轉載的一篇博文,一看就懂,其講解淺顯簡潔,易懂通俗!藉此本身留做備用,也但願能對來串門的碼農們有所幫助!!java
在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();
</script>
ide
這個例子中,咱們爲x.invoke賦值的時候,先運行一個能夠返回一個函數的函數,而後當即執行之,這樣,x.invoke的每一次迭代器時至關與執行這樣的語句:
函數
複製代碼代碼以下:this
//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 CachedSearchBox = (function () {
var cache = {},
count = [];
return {
attachSearchBox: function (dsid) {
if (dsid in cache) {//若是結果在緩存中
return cache[dsid];//直接返回緩存中的對象
}
var fsb = document.getElementById(dsid);//新建
cache[dsid] = fsb;//更新緩存
if (count.length > 100) {//保正緩存的大小<=100
delete cache[count.shift()];
}
return fsb;
},
clearSearchBox: function (dsid) {
if (dsid in cache) {
cache[dsid].clearSelection();
}
}
};
})();
var obj1 = CachedSearchBox.attachSearchBox("input1");
//alert(obj1);
var obj2 = CachedSearchBox.attachSearchBox("input1");
實現封裝:
複製代碼代碼以下:
var person = function(){
//變量做用域爲函數內部,外部沒法訪問
var name = "default";
return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
}();
print(person.name);//直接訪問,結果爲undefined
print(person.getName());
person.setName("jack");
print(person.getName());
獲得結果以下:
undefined
default
jack
閉包的另外一個重要用途是實現面向對象中的對象,傳統的對象語言都提供類的模板機制,這樣不一樣的對象(類的實例)擁有獨立的成員及狀態,互不干涉。雖然JavaScript中沒有類這樣的機制,可是經過使用閉包,咱們能夠模擬出這樣的機制。仍是以上邊的例子來說:
複製代碼代碼以下:
function Person(){
var name = "default";
return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
};
var john = Person();
print(john.getName());
john.setName("john");
print(john.getName());
var jack = Person();
print(jack.getName());
jack.setName("jack");
print(jack.getName());
運行結果以下:
default
john
default
jack
javascript閉包應該注意的問題:
1.內存泄漏:
在不一樣的JavaScript解釋器實現中,因爲解釋器自己的缺陷,使用閉包可能形成內存泄漏,內存泄漏是比較嚴重的問題,會嚴重影響瀏覽器的響應速度,下降用戶體驗,甚至會形成瀏覽器無響應等現象。JavaScript的解釋器都具有垃圾回收機制,通常採用的是引用計數的形式,若是一個對象的引用計數爲零,則垃圾回收機制會將其回收,這個過程是自動的。可是,有了閉包的概念以後,這個過程就變得複雜起來了,在閉包中,由於局部的變量可能在未來的某些時刻須要被使用,所以垃圾回收機制不會處理這些被外部引用到的局部變量,而若是出現循環引用,即對象A引用B,B引用C,而C又引用到A,這樣的狀況使得垃圾回收機制得出其引用計數不爲零的結論,從而形成內存泄漏。
2.上下文的引用:
複製代碼代碼以下:
$(function(){
var con = $("div#panel");
this.id = "content";
con.click(function(){
alert(this.id);//panel
});
});
此處的alert(this.id)到底引用着什麼值呢?不少開發者可能會根據閉包的概念,作出錯誤的判斷:
content
理由是,this.id顯示的被賦值爲content,而在click回調中,造成的閉包會引用到this.id,所以返回值爲content。然而事實上,這個alert會彈出」panel」,究其緣由,就是此處的this,雖然閉包能夠引用局部變量,可是涉及到this的時候,狀況就有些微妙了,由於調用對象的存在,使得當閉包被調用時(當這個panel的click事件發生時),此處的this引用的是con這個jQuery對象。而匿名函數中的this.id = 「content」是對匿名函數自己作的操做。兩個this引用的並不是同一個對象。
若是想要在事件處理函數中訪問這個值,咱們必須作一些改變:
複製代碼代碼以下:
$(function(){
var con = $("div#panel");
this.id = "content";
var self = this;
con.click(function(){
alert(self.id);//content
});
});
這樣,咱們在事件處理函數中保存的是外部的一個局部變量self的引用,而並不是this。這種技巧在實際應用中多有應用,咱們在後邊的章節裏進行詳細討論。關於閉包的更多內容,咱們將在第九章詳細討論,包括討論其餘命令式語言中的「閉包」,閉包在實際項目中的應用等等。