我又來了,隔了這么久才寫第二篇,拖延癥的我~~~
閉包
定義
閉包是個老生常談的話題了,網(wǎng)上也有一大堆相關(guān)的文章,不過既然是筆記,那也簡單提一下吧。
先看wiki中閉包的定義:
閉包(英語:Closure),又稱詞法閉包(Lexical Closure)或函數(shù)閉包(function closures),是引用了自由變量的函數(shù)。這個被引用的自由變量將和這個函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外。
看定義,有兩個重點(diǎn),自由變量和函數(shù)。那什么是自由變量?
先看個最簡單的閉包例子:
function foo(){
var a = 2;
return function bar(){
console.log(a)
};
}
var baz = foo();
baz();//2
想必你也在各種文章看到過類似的例子,那我們就把它往定義中套一套。
我在上篇閱讀筆記中說過,內(nèi)層作用域可以訪問到外層作用域的變量。沒錯,這個bar
訪問到的外部變量a
,相對于bar
來說就是一個自由變量。這其實(shí)也就滿足了最廣義的閉包定義了,但我們js中通俗意義上的閉包是還要滿足后面這句
這個被引用的自由變量將和這個函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外。
離開了創(chuàng)造它的環(huán)境。是的,經(jīng)過foo
那條語句的執(zhí)行,bar
已經(jīng)被返回并賦值到baz
中,也就是暴露在了全局作用域中,離開了創(chuàng)造它的環(huán)境。
這個被引用的自由變量將和這個函數(shù)一同存在。也就是說,變量a
(連同整個內(nèi)部作用域)并不會因?yàn)?code>foo被執(zhí)行完了就消失,而是會保留在內(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++
,你會看到,每次運(yùn)行baz
后a
的值是累加的,而不是2
。
綜上,這個baz
就是我們通常所說的閉包了。
常見閉包
一個很常見的例子就是在for循環(huán)里使用閉包
如下面
for(var i= 1; i <= 5; i++){
setTimeout( function timer(){
console.log(i);
},i*1000);
}
你可會以為這段代碼是以一秒的間隔輸出1~5
。
但實(shí)際上,這段代碼在運(yùn)行時會以每秒一次的頻率輸出五次6
。
6
是怎么來的?在循環(huán)結(jié)束后,i
的值為6
。也就是說,timer
里面的打印i
的在循環(huán)結(jié)束后的i
。
仔細(xì)想想也的確如此,setTimeout
的回調(diào)是在循環(huán)結(jié)束后才調(diào)用的,我們期望每次循環(huán)中會有一個i
的副本被保存timer
中,以至于在后面輸出,但事實(shí)上for
循環(huán)并沒有提供這樣的機(jī)制,所以每次輸出的i
都是在循環(huán)結(jié)束后的值6
,i
是存在于全局作用域中被共享的。
這個時候可以用閉包解決
for(var i= 1; i <= 5; i++){
(function(){
var j = i;
setTimeout( function timer(){
console.log(j);
},j*1000);
})();
}
或者更常見的寫法是這樣
for(var i= 1; i <= 5; i++){
(function(i){
setTimeout( function timer(){
console.log(i);
},i*1000);
})(i);
}
我們用一個立即執(zhí)行的匿名函數(shù)來構(gòu)造一個封閉的內(nèi)部作用域,復(fù)制一個i
的副本,與timer
一起構(gòu)成一個閉包,從而達(dá)到每次保存i
的值的目的。
這便是閉包的常見用法。
PS:ES6中,我們可以直接用
let
來代替var
,生成塊級作用域,達(dá)到同樣的效果而不用閉包。
總結(jié)
無論你懂不懂閉包,我想你的代碼中已經(jīng)有意無意地存在了閉包。
閉包作用有好多,變量封裝,私有化;函數(shù)嵌套;函數(shù)可以很方便地訪問到外部變量等。但不合理的應(yīng)用也會很容易造成內(nèi)存泄漏,代碼混亂等問題。
重點(diǎn)是要理解閉包及其背后的作用域機(jī)制,這樣才能更好用閉包來為我們服務(wù)。