基于javascript 閉包基礎(chǔ)分享
如果對(duì)作用域,函數(shù)為獨(dú)立的對(duì)象這樣的基本概念理解較好的話,理解閉包的概念并在實(shí)際的編程實(shí)踐中應(yīng)用則頗有水到渠成之感。
在DOM的事件處理方面,大多數(shù)程序員甚至自己已經(jīng)在使用閉包了而不自知,在這種情況下,對(duì)于瀏覽器中內(nèi)嵌的JavaScript引擎的bug可能造成內(nèi)存泄漏這一問題姑且不論,就是程序員自己調(diào)試也常常會(huì)一頭霧水。
用簡(jiǎn)單的語句來描述JavaScript中的閉包的概念:由于JavaScript中,函數(shù)是對(duì)象,對(duì)象是屬性的集合,而屬性的值又可以是對(duì)象,則在函數(shù)內(nèi)定義函數(shù)成為理所當(dāng)然,如果在函數(shù)func內(nèi)部聲明函數(shù)inner,然后在函數(shù)外部調(diào)用inner,這個(gè)過程即產(chǎn)生了一個(gè)閉包?! ?BR>閉包的特性:
我們先來看一個(gè)例子,如果不了解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);
}
}
//調(diào)用這個(gè)函數(shù)
clouseTest();
print(outter[0].invoke());
print(outter[1].invoke());
print(outter[2].invoke());
print(outter[3].invoke());
運(yùn)行的結(jié)果如何呢?很多初學(xué)者可能會(huì)得出這樣的答案:
0
1
2
3
然而,運(yùn)行這個(gè)程序,得到的結(jié)果為:
4
4
4
4
其實(shí),在每次迭代的時(shí)候,這樣的語句x.invoke = function(){print(i);}并沒有被執(zhí)行,只是構(gòu)建了一個(gè)函數(shù)體為”print(i);”的函數(shù)對(duì)象,如此而已。而當(dāng)i=4時(shí),迭代停止,外部函數(shù)返回,當(dāng)再去調(diào)用outter[0].invoke()時(shí),i的值依舊為4,因此outter數(shù)組中的每一個(gè)元素的invoke都返回i的值:4。如何解決這一問題呢?我們可以聲明一個(gè)匿名函數(shù),并立即執(zhí)行它:
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>
這個(gè)例子中,我們?yōu)閤.invoke賦值的時(shí)候,先運(yùn)行一個(gè)可以返回一個(gè)函數(shù)的函數(shù),然后立即執(zhí)行之,這樣,x.invoke的每一次迭代器時(shí)相當(dāng)與執(zhí)行這樣的語句:
//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);}
這樣就可以得到正確結(jié)果了。閉包允許你引用存在于外部函數(shù)中的變量。然而,它并不是使用該變量創(chuàng)建時(shí)的值,相反,它使用外部函數(shù)中該變量最后的值。
閉包的用途:
現(xiàn)在,閉包的概念已經(jīng)清晰了,我們來看看閉包的用途。事實(shí)上,通過使用閉包,我們可以做很多事情。比如模擬面向?qū)ο蟮拇a風(fēng)格;更優(yōu)雅,更簡(jiǎn)潔的表達(dá)出代碼;在某些方面提升代碼的執(zhí)行效率。
緩存:
再來看一個(gè)例子,設(shè)想我們有一個(gè)處理過程很耗時(shí)的函數(shù)對(duì)象,每次調(diào)用都會(huì)花費(fèi)很長(zhǎng)時(shí)間,那么我們就需要將計(jì)算出來的值存儲(chǔ)起來,當(dāng)調(diào)用這個(gè)函數(shù)的時(shí)候,首先在緩存中查找,如果找不到,則進(jìn)行計(jì)算,然后更新緩存并返回值,如果找到了,直接返回查找到的值即可。
閉包正是可以做到這一點(diǎn),因?yàn)樗粫?huì)釋放外部的引用,從而函數(shù)內(nèi)部的值可以得以保留。
var CachedSearchBox = (function () {
var cache = {},
count = [];
return {
attachSearchBox: function (dsid) {
if (dsid in cache) {//如果結(jié)果在緩存中
return cache[dsid];//直接返回緩存中的對(duì)象
}
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");
實(shí)現(xiàn)封裝:
var person = function(){
//變量作用域?yàn)楹瘮?shù)內(nèi)部,外部無法訪問
var name = "default";
return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
}();
print(person.name);//直接訪問,結(jié)果為undefined
print(person.getName());
person.setName("jack");
print(person.getName());
得到結(jié)果如下:
undefined
default
jack
閉包的另一個(gè)重要用途是實(shí)現(xiàn)面向?qū)ο笾械膶?duì)象,傳統(tǒng)的對(duì)象語言都提供類的模板機(jī)制,這樣不同的對(duì)象(類的實(shí)例)擁有獨(dú)立的成員及狀態(tài),互不干涉。雖然JavaScript中沒有類這樣的機(jī)制,但是通過使用閉包,我們可以模擬出這樣的機(jī)制。還是以上邊的例子來講:
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());
運(yùn)行結(jié)果如下:
default
john
default
jack
javascript閉包應(yīng)該注意的問題:
1.內(nèi)存泄漏:
在不同的JavaScript解釋器實(shí)現(xiàn)中,由于解釋器本身的缺陷,使用閉包可能造成內(nèi)存泄漏,內(nèi)存泄漏是比較嚴(yán)重的問題,會(huì)嚴(yán)重影響瀏覽器的響應(yīng)速度,降低用戶體驗(yàn),甚至?xí)斐蔀g覽器無響應(yīng)等現(xiàn)象。JavaScript的解釋器都具備垃圾回收機(jī)制,一般采用的是引用計(jì)數(shù)的形式,如果一個(gè)對(duì)象的引用計(jì)數(shù)為零,則垃圾回收機(jī)制會(huì)將其回收,這個(gè)過程是自動(dòng)的。但是,有了閉包的概念之后,這個(gè)過程就變得復(fù)雜起來了,在閉包中,因?yàn)榫植康淖兞靠赡茉趯淼哪承r(shí)刻需要被使用,因此垃圾回收機(jī)制不會(huì)處理這些被外部引用到的局部變量,而如果出現(xiàn)循環(huán)引用,即對(duì)象A引用B,B引用C,而C又引用到A,這樣的情況使得垃圾回收機(jī)制得出其引用計(jì)數(shù)不為零的結(jié)論,從而造成內(nèi)存泄漏。
2.上下文的引用:
$(function(){
var con = $("div#panel");
this.id = "content";
con.click(function(){
alert(this.id);//panel
});
});
此處的alert(this.id)到底引用著什么值呢?很多開發(fā)者可能會(huì)根據(jù)閉包的概念,做出錯(cuò)誤的判斷:
content
理由是,this.id顯示的被賦值為content,而在click回調(diào)中,形成的閉包會(huì)引用到this.id,因此返回值為content。然而事實(shí)上,這個(gè)alert會(huì)彈出”panel”,究其原因,就是此處的this,雖然閉包可以引用局部變量,但是涉及到this的時(shí)候,情況就有些微妙了,因?yàn)檎{(diào)用對(duì)象的存在,使得當(dāng)閉包被調(diào)用時(shí)(當(dāng)這個(gè)panel的click事件發(fā)生時(shí)),此處的this引用的是con這個(gè)jQuery對(duì)象。而匿名函數(shù)中的this.id = “content”是對(duì)匿名函數(shù)本身做的操作。兩個(gè)this引用的并非同一個(gè)對(duì)象。
如果想要在事件處理函數(shù)中訪問這個(gè)值,我們必須做一些改變:
$(function(){
var con = $("div#panel");
this.id = "content";
var self = this;
con.click(function(){
alert(self.id);//content
});
});
這樣,我們?cè)谑录幚砗瘮?shù)中保存的是外部的一個(gè)局部變量self的引用,而并非this。這種技巧在實(shí)際應(yīng)用中多有應(yīng)用,我們?cè)诤筮叺恼鹿?jié)里進(jìn)行詳細(xì)討論。關(guān)于閉包的更多內(nèi)容,我們將在第九章詳細(xì)討論,包括討論其他命令式語言中的“閉包”,閉包在實(shí)際項(xiàng)目中的應(yīng)用等等。
附:由于本身水平有限,文中難免有紕漏錯(cuò)誤等,或者語言本身有不妥當(dāng)之處,歡迎及時(shí)指正,提出建議。本文只為拋磚引玉,謝謝大家!
相關(guān)文章
Javascript入門學(xué)習(xí)第八篇 js dom節(jié)點(diǎn)屬性說明
上2篇文章我們講了 用dom方式 創(chuàng)建節(jié)點(diǎn),復(fù)制節(jié)點(diǎn),插入節(jié)點(diǎn), 刪除節(jié)點(diǎn),替換節(jié)點(diǎn),查找節(jié)點(diǎn),獲取屬性等。。。2008-07-07談?wù)勱P(guān)于JavaScript 中的 MVC 模式
本文介紹了模型-視圖-控制器模式在 JavaScript 中的實(shí)現(xiàn),有需要的朋友可以參考一下2013-04-04Javascript - 全面理解 caller,callee,call,apply
Javascript - 全面理解 caller,callee,call,apply...2007-01-01Mobile Web開發(fā)基礎(chǔ)之四--處理手機(jī)設(shè)備的橫豎屏問題
這篇文章主要介紹了Mobile Web開發(fā)基礎(chǔ)之-—處理手機(jī)設(shè)備的橫豎屏,window.orientation屬性與onorientationchange事件以及media query方式是開發(fā)過程中需要注意到的兩種解決方式,需要的朋友可以參考下2017-08-08JavaScript學(xué)習(xí)筆記(一) js基本語法
JavaScript學(xué)習(xí)筆記(一) js基本語法,想要學(xué)習(xí)js的朋友可以參考下。2011-10-10Javascript學(xué)習(xí)筆記9 prototype封裝繼承
在上文中,我利用prototype的原理做了一個(gè)封裝的New,然后我就想到,我是否可以用prototype的原理進(jìn)一步封裝面向?qū)ο蟮囊恍┗咎卣髂??比如繼承。2010-01-01