一、閉包(Closures)的定義
描述閉包定義之前需要先了解一個(gè)學(xué)術(shù)名詞--(free variable)自由變量纫骑,網(wǎng)絡(luò)上的解釋是:一些被某個(gè)方法使用的變量,且這些變量既不是方法中定義的變量也不是方法的參數(shù)隔显。
官方對閉包的描述也是非常的簡單项鬼,就一句話:
閉包是使用自由變量的方法。
1.1 如何創(chuàng)建一個(gè)閉包
根據(jù)閉包的定義耻陕,相信很難有人能一下子理解它的含義拙徽,下面就使用代碼來說明:
function a() {
var n = 1;
function b() {
console.log(n++);
}
}
觀察以上代碼可以發(fā)現(xiàn)以下特點(diǎn):
- 方法a中內(nèi)嵌了方法b
- 方法b使用了方法a中的變量
其實(shí)當(dāng)JavaScript解釋器執(zhí)行完這段代碼時(shí),并沒有產(chǎn)生閉包诗宣,為什么呢膘怕?雖然我們可以看到方法b使用外部的變量n,但是此時(shí)a中的邏輯并沒有執(zhí)行召庞,不管是n還是b岛心,這些只是方法a的描述信息而已,此時(shí)JavaScript環(huán)境中并沒有方法b存在篮灼。那何時(shí)才能產(chǎn)生閉包呢忘古,答案是:當(dāng)JavaScript解釋器定義方法b的時(shí)候。當(dāng)我們執(zhí)行a();
時(shí)诅诱,a中的語句得以執(zhí)行髓堪,首先創(chuàng)建了變量n,然后定義了方法b,而定義方法b時(shí)需要外部的變量n旦袋,此時(shí)才符合閉包定義的描述骤菠,這時(shí)方法b才是一個(gè)閉包。
二疤孕、閉包的應(yīng)用
2.1 面向?qū)ο蟮乃季S
由上例可以看出商乎,一個(gè)閉包由兩部分組成:自由變量和使用自由變量的方法;面向?qū)ο蟮幕驹鼐褪菍ο蠹婪В瑢ο蠛袑傩院头椒钠荩划?dāng)我們把閉包看做一個(gè)對象時(shí),自由變量就是屬性专控,而使用自有變量的方法就是方法抹凳;由于單一的閉包指的是使用自由變量的方法,終歸還是一個(gè)方法伦腐,所以這種對象本質(zhì)還是一個(gè)方法赢底。
比如有這樣一個(gè)需求:小明今年10歲,小王今年20歲柏蘑,小李今年30歲幸冻,請問5年后,他們分別多少歲咳焚?使用面向?qū)ο蟮乃枷刖帉懘a如下:
// 從閉包的角度看洽损,這里我們相當(dāng)于定義了一個(gè)類,它的屬性是:name革半,方法是:future()
function man(name, age) {
function future() {
console.log(name + "未來的年齡:" + (age + 5));
}
return future;
};
// 創(chuàng)建三個(gè)對象
// xiaomingObj對象的屬性:name = "xiaoming"碑定,方法:future()
var xiaomingObj = man("xiaoming", 10);
// 打印:xiaoming未來的年齡:15
xiaomingObj();
// xiaowangObj 對象的屬性:name = "xiaowang"又官;方法:future()
var xiaowangObj = man("xiaowang", 20);
// 打友恿酢:xiaowang未來的年齡:15
xiaowangObj();
// xiaoliObj對象的屬性:name = "xiaoli";方法:future()
var xiaoliObj = man("xiaoli", 30);
// 打恿础:xiaoli未來的年齡:15
xiaoliObj();
2.2 封裝私有變量和方法
這是閉包最重要的用途了访娶,因?yàn)殚]包使用的自由變量對外部不可見,所以可以起到私有化的作用觉阅,只向外提供必要的接口崖疤,封裝內(nèi)部邏輯。正是因?yàn)橛辛碎]包典勇,才為JavaScript的模塊化編程奠定了基礎(chǔ)劫哼。
封裝私有變量和方法示例:
var human = (function() {
// 私有變量
var name = "林";
// 私有方法
var think = function() {
// 心想還是讓別人叫我小林吧
return "小" + name;
};
// 提供的外部接口
return {
speak : function() {
console.log("你好,可以叫我" + think());
}
};
})();
// 打痈铙稀:你好权烧,可以叫我小林
human.speak();
代碼說明:
外層是一個(gè)匿名方法:function(){...}
眯亦,通過立即執(zhí)行的方式(用括號(hào)包裹起來作為方法表達(dá)式,并使用()立即執(zhí)行)創(chuàng)建了一個(gè)閉包:speak及其引用的think般码;外部的human無法直接訪問name與think妻率,只能訪問對外開放的speak,從而達(dá)到封裝的效果板祝。
三宫静、循環(huán)中的誤區(qū)
對于新手來說,使用閉包經(jīng)常會(huì)犯的一個(gè)錯(cuò)誤是:在循環(huán)中創(chuàng)建閉包券时。請分析以下代碼:
function a() {
var b = null;
for (var i = 0; i < 3; i++) {
if (i === 1) {
b = function() {
console.log(i);
}
}
}
return b;
}
// 你覺得會(huì)打印什么孤里?
a()();
如果你覺得會(huì)打印出1,那恭喜你進(jìn)坑了橘洞;說明你還沒有完全理解閉包捌袜,正確的答案是3,為什么呢炸枣?因?yàn)閳?zhí)行了a()后虏等,創(chuàng)建的閉包所包含的自由變量是i,當(dāng)a()執(zhí)行完畢后适肠,i被更新為3霍衫,此時(shí)b還沒有被調(diào)用,當(dāng)執(zhí)行a()()時(shí)迂猴,b才被調(diào)用慕淡,所以打印出了3背伴。
四沸毁、性能考慮
閉包對性能和內(nèi)存都有相對較高的消耗,除非特殊場景的需要傻寂,不要隨便使用閉包息尺;閉包相當(dāng)于通過方法來定義方法,每次創(chuàng)建閉包時(shí)都會(huì)對方法重新定義疾掰;當(dāng)閉包被外部引用時(shí)搂誉,由于閉包內(nèi)的自由變量無法釋放,也會(huì)導(dǎo)致內(nèi)存無法釋放静檬。