我又來(lái)了婆跑,隔了這么久才寫(xiě)第二篇吧享,拖延癥的我~~~
閉包
定義
閉包是個(gè)老生常談的話題了魏割,網(wǎng)上也有一大堆相關(guān)的文章,不過(guò)既然是筆記钢颂,那也簡(jiǎn)單提一下吧钞它。
先看wiki中閉包的定義:
閉包(英語(yǔ):Closure),又稱詞法閉包(Lexical Closure)或函數(shù)閉包(function closures),是引用了自由變量的函數(shù)遭垛。這個(gè)被引用的自由變量將和這個(gè)函數(shù)一同存在尼桶,即使已經(jīng)離開(kāi)了創(chuàng)造它的環(huán)境也不例外。
看定義锯仪,有兩個(gè)重點(diǎn)泵督,自由變量和函數(shù)。那什么是自由變量庶喜?
先看個(gè)最簡(jiǎn)單的閉包例子:
function foo(){
var a = 2;
return function bar(){
console.log(a)
};
}
var baz = foo();
baz();//2
想必你也在各種文章看到過(guò)類(lèi)似的例子小腊,那我們就把它往定義中套一套。
我在上篇閱讀筆記中說(shuō)過(guò)溃卡,內(nèi)層作用域可以訪問(wèn)到外層作用域的變量溢豆。沒(méi)錯(cuò),這個(gè)bar
訪問(wèn)到的外部變量a
瘸羡,相對(duì)于bar
來(lái)說(shuō)就是一個(gè)自由變量漩仙。這其實(shí)也就滿足了最廣義的閉包定義了,但我們js中通俗意義上的閉包是還要滿足后面這句
這個(gè)被引用的自由變量將和這個(gè)函數(shù)一同存在犹赖,即使已經(jīng)離開(kāi)了創(chuàng)造它的環(huán)境也不例外队他。
離開(kāi)了創(chuàng)造它的環(huán)境。是的峻村,經(jīng)過(guò)foo
那條語(yǔ)句的執(zhí)行麸折,bar
已經(jīng)被返回并賦值到baz
中,也就是暴露在了全局作用域中粘昨,離開(kāi)了創(chuàng)造它的環(huán)境垢啼。
這個(gè)被引用的自由變量將和這個(gè)函數(shù)一同存在。也就是說(shuō)张肾,變量a
(連同整個(gè)內(nèi)部作用域)并不會(huì)因?yàn)?code>foo被執(zhí)行完了就消失芭析,而是會(huì)保留在內(nèi)存中。
就像下面的例子
function foo(){
var a = 2;
return function bar(){
console.log(a++)
};
}
var baz = foo();
baz();//2
baz();//3
baz();//4
baz();//5
在bar
中執(zhí)行了a++
吞瞪,你會(huì)看到馁启,每次運(yùn)行baz
后a
的值是累加的,而不是2
芍秆。
綜上惯疙,這個(gè)baz
就是我們通常所說(shuō)的閉包了。
常見(jiàn)閉包
一個(gè)很常見(jiàn)的例子就是在for循環(huán)里使用閉包
如下面
for(var i= 1; i <= 5; i++){
setTimeout( function timer(){
console.log(i);
},i*1000);
}
你可會(huì)以為這段代碼是以一秒的間隔輸出1~5
妖啥。
但實(shí)際上霉颠,這段代碼在運(yùn)行時(shí)會(huì)以每秒一次的頻率輸出五次6
。
6
是怎么來(lái)的荆虱?在循環(huán)結(jié)束后掉分,i
的值為6
俭缓。也就是說(shuō),timer
里面的打印i
的在循環(huán)結(jié)束后的i
酥郭。
仔細(xì)想想也的確如此,setTimeout
的回調(diào)是在循環(huán)結(jié)束后才調(diào)用的愿吹,我們期望每次循環(huán)中會(huì)有一個(gè)i
的副本被保存timer
中不从,以至于在后面輸出,但事實(shí)上for
循環(huán)并沒(méi)有提供這樣的機(jī)制犁跪,所以每次輸出的i
都是在循環(huán)結(jié)束后的值6
椿息,i
是存在于全局作用域中被共享的。
這個(gè)時(shí)候可以用閉包解決
for(var i= 1; i <= 5; i++){
(function(){
var j = i;
setTimeout( function timer(){
console.log(j);
},j*1000);
})();
}
或者更常見(jiàn)的寫(xiě)法是這樣
for(var i= 1; i <= 5; i++){
(function(i){
setTimeout( function timer(){
console.log(i);
},i*1000);
})(i);
}
我們用一個(gè)立即執(zhí)行的匿名函數(shù)來(lái)構(gòu)造一個(gè)封閉的內(nèi)部作用域坷衍,復(fù)制一個(gè)i
的副本寝优,與timer
一起構(gòu)成一個(gè)閉包,從而達(dá)到每次保存i
的值的目的枫耳。
這便是閉包的常見(jiàn)用法乏矾。
PS:ES6中,我們可以直接用
let
來(lái)代替var
迁杨,生成塊級(jí)作用域钻心,達(dá)到同樣的效果而不用閉包。
總結(jié)
無(wú)論你懂不懂閉包铅协,我想你的代碼中已經(jīng)有意無(wú)意地存在了閉包捷沸。
閉包作用有好多,變量封裝狐史,私有化痒给;函數(shù)嵌套;函數(shù)可以很方便地訪問(wèn)到外部變量等骏全。但不合理的應(yīng)用也會(huì)很容易造成內(nèi)存泄漏苍柏,代碼混亂等問(wèn)題。
重點(diǎn)是要理解閉包及其背后的作用域機(jī)制吟温,這樣才能更好用閉包來(lái)為我們服務(wù)序仙。