閉包是什么
閉包就是一個擁有變量和綁定了這些變量的環(huán)境的表達式(通常是一個函數(shù)),是一個有權(quán)訪問其外部作用域中變量的函數(shù)观蓄。最常見的方式就是在某函數(shù)內(nèi)部創(chuàng)建一個函數(shù)。
- 優(yōu)點
能夠讀取函數(shù)內(nèi)部的變量祠墅,并且讓這些變量的值始終保存在內(nèi)存中侮穿。使用閉包和匿名自執(zhí)行函數(shù)實現(xiàn)模塊化。 - 缺點
1.既然不釋放內(nèi)存毁嗦,則必然會對內(nèi)存的消耗很大亲茅,造成頁面訪問的性能問題
2.閉包會改變父函數(shù)的私有屬性,在你不經(jīng)意的時候
一個經(jīng)典的例子
function test(){
for(var i = 0; i < 10 ; i++){
setTimeout(function(){
console.log(i)
}, 0);
}
}
test();
我相信大家都會瞬間給出答案:輸出是個10狗准,原因如下:
-
test()
執(zhí)行時會創(chuàng)建一個運行時期的上下文克锣,而setTimeout內(nèi)部的函數(shù)會放在for循環(huán)隊列之后,等到for循環(huán)執(zhí)行完之后才開始執(zhí)行腔长。 - function(){console.log(i)}執(zhí)行時首先會尋找函數(shù)內(nèi)部的變量i袭祟。此時找不到i,再尋找test中的i
- (閉包的概念:訪問函數(shù)外的變量饼酿,這些變量只有等到閉包不使用才會被銷毀)此時的i值已經(jīng)變?yōu)榱?0榕酒,所以十次執(zhí)行都會輸出10胚膊。
解決這個問題的方法如下
function test(){
for(var i = 0; i < 10 ; i++){
(function(li){
setTimeout(function(){
console.log(li)
}, 0);
})(i);
}
}
test();
再舉一個例子
function f1(){
var n=999;
nAdd=function(){n+=1}//定義了一個全局變量故俐,相當于setter,方便在函數(shù)外部對函數(shù)內(nèi)部的變量進行操作紊婉,不是本文要說明的重點
function f2(){
console.log(n);
}
return f2;
}
var result=f1();
result(); //返回內(nèi)部函數(shù)f2,f2訪問外部函數(shù)f1的局部變量n药版,999
nAdd(); // f1函數(shù)聲明后,產(chǎn)生window.nAdd
result(); // 1000喻犁,可見變量n保存在了內(nèi)存中槽片;
var result2 = f1();
result2();//999 這里的值為什么不是1000呢何缓?帶著疑問往下看
一個函數(shù),當沒有依賴關(guān)系存在時还栓,就會有被垃圾回收機制回收的可能碌廓,但是如果像上文案例f1()那樣,將內(nèi)部函數(shù)(f2)作為返回值賦值給一個全局變量剩盒,則會改變這種潛在的關(guān)系谷婆。這種即使離開函數(shù)作用域仍能通過引用來調(diào)用內(nèi)部函數(shù)(或變量)的事實,意味著辽聊,只要存在調(diào)用內(nèi)部函數(shù)的可能纪挎,JavaScript就需要保留被該內(nèi)部函數(shù)引用的函數(shù)(即所謂的外部函數(shù))
JavaScript運行時會跟蹤引用這個內(nèi)部函數(shù)的所有變量,知道最后一個變量廢棄跟匆,JavaScript垃圾回收機制才能釋放對應的空間(將內(nèi)部函數(shù)置為null)
根據(jù)上面的論述,我們來分析一下第二個例子:
- f1()函數(shù)的返回值是內(nèi)部函數(shù)f2()并將返回值賦值給了全局變量result
- f2()保存有f1()的的依賴關(guān)系异袄,導致f2()始終在內(nèi)存中未被清除,可以在外部訪問到f1()中的變量
- result2()執(zhí)行后打印了999玛臂,而不是1000烤蜕。是因為創(chuàng)建新的封閉環(huán)境,本質(zhì)上是創(chuàng)建了一個新的對象垢揩,而閉包就是這個對象的實例方法
小測試
第一題:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());
答案是:'The Window'
這樣理解 var fun = object.getNameFunc();
這個返回的是一個function
fun = function(){
return this.name;
}
此時玖绿,fun中的this指向是window,所以this.name是The window
這道題是典型的閉包沒閉上~要么用聰明的that來解決叁巨,要么使用 真·閉包
// 聰明的that
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;//這里的that現(xiàn)在是相當于object這個對象了
return function(){
return that.name;//所以這里面輸出的是object.name,也就是"My Object"
};
}
};
alert(object.getNameFunc()());
// 真·閉包術(shù)
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : (function(){
return function(){
return this.name;
};
})()
};
alert(object.getNameFunc()); //My Object