閉包是javascript的一個(gè)難點(diǎn)厌殉,也是一個(gè)重點(diǎn),很多高級應(yīng)用都需要用到閉包。在學(xué)習(xí)閉包的過程中看了不少書和大牛的博客适肠,下面來說說我對閉包的理解和看法。
函數(shù)
要說閉包就不得不先說一下函數(shù)候引,這個(gè)javascript的中流砥柱侯养。
眾所周知在ES6之前js都沒有class這個(gè)關(guān)鍵字,一切的面向?qū)ο蠖际峭ㄟ^function模擬的澄干。
一個(gè)簡單到不能再簡單的例子:函數(shù)內(nèi)部可以訪問外部的變量(全局變量)逛揩。
var n=100;
function f1()
{
console.log(n);
}
f1();//100
而外部卻不能訪問函數(shù)內(nèi)的局部變量。
function f1()
{
var n=100;
}
console.log(n);//n is not defined
那么問題來了麸俘,如果我想訪問函數(shù)內(nèi)的局部變量怎么辦辩稽?一種解決辦法就是使用閉包。
什么是閉包从媚?我個(gè)人認(rèn)為函數(shù)中的函數(shù)就是閉包逞泄,有點(diǎn)像java的內(nèi)部類,既然函數(shù)可以訪問外部變量,那么函數(shù)中的函數(shù)也可以訪問外層函數(shù)的變量喷众,這個(gè)內(nèi)層函數(shù)就是閉包各谚。
function f1()
{
var n=100;
function f2()
{
console.log(n);
}
f2();
}
f1();//100
上述寫法比較麻煩,因此可以把f2作為一個(gè)返回值返回給f1到千。
function f1()
{
var n=100;
function f2()
{
console.log(n);
}
return f2;
}
f1()();//100
//相當(dāng)于var f=f1();f();
下面來看一個(gè)問題:這里有5個(gè)li昌渤,名字是0到4,我希望我點(diǎn)擊0的時(shí)候在控制臺輸出0憔四,點(diǎn)擊1的時(shí)候在控制臺輸出1愈涩,以此類推。
var lis=document.getElementsByTagName("li");
for(var i=0;i<lis.length;i++)
{
lis[i].onclick=function()
{
console.log(i);
}
}
奇怪的事情發(fā)生了加矛,無論我點(diǎn)擊哪一個(gè)在控制臺輸出的都是5履婉。
這是為什么呢?
仔細(xì)觀察斟览,我為每個(gè)li創(chuàng)建了一個(gè)匿名函數(shù)毁腿,匿名函數(shù)形成了閉包,它們都在引用外部的i苛茂,當(dāng)i改變時(shí)已烤,自然所有匿名函數(shù)里面的i都改變了。用幾張圖來說明妓羊。
當(dāng)i增加到5的時(shí)候不再滿足循環(huán)條件胯究,由于五個(gè)匿名函數(shù)都是引用同一個(gè)i,所以它們打印出來的都是5躁绸。
既然它們是引用同一個(gè)i裕循,那么只需為每一個(gè)i創(chuàng)建一個(gè)備份:
var lis=document.getElementsByTagName("li");
for(var i=0;i<lis.length;i++)
{
lis[i].onclick=function(num)
{
return function()
{
console.log(num);
}
}(i)
}
這里在匿名函數(shù)接收一個(gè)參數(shù)并且立即執(zhí)行,匿名函數(shù)內(nèi)部又創(chuàng)建了一個(gè)函數(shù)負(fù)責(zé)把這個(gè)參數(shù)返回給匿名函數(shù)净刮,由于是按值傳遞的剥哑,所以相當(dāng)于給i做了一個(gè)備份。就算i一直在改變淹父,而每個(gè)匿名函數(shù)的num是固定的株婴。
閉包中的this
在閉包中使用this會出現(xiàn)一些問題,在全局函數(shù)中this指向的是window暑认,當(dāng)函數(shù)以方法被調(diào)用時(shí)困介,this就指向這個(gè)對象。但是蘸际,匿名函數(shù)的執(zhí)行環(huán)境具有全局性座哩,this一般指向window。參考紅寶石書中的一個(gè)經(jīng)典的問題:
var name="The Window";
var object={
name:"My Object",
getNameFunc:function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());//"The Window"
內(nèi)部函數(shù)在搜索this的時(shí)候只會搜到其活動(dòng)對象為止捡鱼,由于匿名函數(shù)的執(zhí)行環(huán)境具有全局性八回,因此當(dāng)前的活動(dòng)對象就是window。
可以通過改變當(dāng)前的活動(dòng)對象來改變this的指向:
var name="The Window";
var object={
name:"My Object",
getNameFunc:function(){
var that=this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()());//"My Object"
這里把this對象賦值給了一個(gè)that變量驾诈,因此當(dāng)前的活動(dòng)對象就從window變成了object缠诅。
閉包引發(fā)的問題
閉包雖好,但是也引發(fā)了一系列問題乍迄,尤其是內(nèi)存泄漏管引。js的垃圾回收機(jī)制是引用計(jì)數(shù)的,當(dāng)一個(gè)變量不再使用的時(shí)候(引用計(jì)數(shù)為0)垃圾回收機(jī)制就會把它清理掉闯两。而閉包的存在會導(dǎo)致引用數(shù)至少為1褥伴。
function f()
{
var myDiv=document.getElementById("Div1");
myDiv.onclick=function()
{
console.log(myDiv.id);
};
}
上述代碼中,函數(shù)f需要等myDiv清除后才能被清除漾狼,而myDiv由于匿名函數(shù)的存在重慢,它的引用數(shù)至少為1,因此它所占用的內(nèi)存永遠(yuǎn)不會被回收逊躁。
解決辦法:
function f()
{
var myDiv=document.getElementById("Div1");
var id=myDiv.id
myDiv.onclick=function()
{
console.log(id);
};
myDiv=null;
}
把myDiv.id保存在一個(gè)變量中似踱,并且在閉包中引用這個(gè)變量,這樣就與myDiv沒有關(guān)系了稽煤。但是閉包會引用包含函數(shù)的整個(gè)活動(dòng)對象核芽,因此必須把myDiv設(shè)為null來解除對它的引用,確保垃圾回收機(jī)制能夠把資源回收酵熙。
使用閉包應(yīng)該注意的問題:閉包雖然好轧简,但是在實(shí)際應(yīng)用中盡量減少 閉包的使用,并且為了防止內(nèi)存泄漏匾二,要時(shí)刻記得在閉包完后清除它的局部變量哮独。