閉包(Closure)概念
在A函數(shù)中定義了一個(gè)B函數(shù),在B函數(shù)中使用了A函數(shù)中的變量,就會(huì)產(chǎn)生閉包,其中B就是一個(gè)閉包。
也可以說,定義在一個(gè)函數(shù)內(nèi)部的這個(gè)函數(shù)就是閉包。
理解閉包需要的幾個(gè)相關(guān)概念
1.變量的作用域
在ES5中變量有兩個(gè)作用域:
- 全局作用域(global)
- 局部作用域(local)
在ES6中補(bǔ)充了一個(gè)新的作用域-塊級(jí)作用域(block)
當(dāng)定義變量的地方?jīng)]有被 function 包括則是全局變量,否則就是局部變量。
在函數(shù)的內(nèi)部會(huì)優(yōu)先使用局部變量(即使全局變量和局部變量同名亦是如此),在函數(shù)調(diào)用的過程,會(huì)給局部變量創(chuàng)建一個(gè)函數(shù)棧區(qū)(區(qū)別于全局棧),來保存這些局部變量。正常情況下在函數(shù)調(diào)用結(jié)束后,函數(shù)棧會(huì)被垃圾回收機(jī)制處理。
2.執(zhí)行上下文
- 代碼的運(yùn)行會(huì)產(chǎn)生執(zhí)行上下文,如果代碼不運(yùn)行就沒有。
- 全局代碼產(chǎn)生全局上下文
- 函數(shù)代碼產(chǎn)生函數(shù)上下文
- 函數(shù)嵌套調(diào)用形成執(zhí)行上下文棧
- 執(zhí)行上下文中保存了執(zhí)行代碼所需要的各類的數(shù)據(jù)
- 執(zhí)行上下文是一個(gè)對(duì)象,在代碼運(yùn)行過程中產(chǎn)生,代碼運(yùn)行完成后就消失.
執(zhí)行上下文的組成
- 自己的執(zhí)行上下文
- 父級(jí)函數(shù)的執(zhí)行上下文
全局上下文
一旦<script></script>標(biāo)簽中代碼運(yùn)行起來,就會(huì)產(chǎn)生一個(gè)執(zhí)行上下文,這個(gè)執(zhí)行上下文就是全局執(zhí)行上下文.
- 全局上下文只有一個(gè)
- 全局執(zhí)行環(huán)境是window對(duì)象,所有變量和函數(shù)都作為window對(duì)象的屬性和方法創(chuàng)建的
- 所有的代碼都在全局執(zhí)行上下文中執(zhí)行
函數(shù)執(zhí)行上下文
每次調(diào)用函數(shù)都會(huì)產(chǎn)生一個(gè)執(zhí)行上下文,函數(shù)調(diào)用完成后,會(huì)把執(zhí)行上下文釋放掉。
按照函數(shù)的調(diào)用順序,這些上下文以棧的形式存儲(chǔ)(先進(jìn)后出).棧底是全局上下文。
3.詞法作用域-靜態(tài)作用域
js代碼的書寫順序,就決定了變量的作用域。換言之,在函數(shù)內(nèi)部去訪問一個(gè)變量,應(yīng)該去定義這個(gè)函數(shù)(寫這個(gè)函數(shù)的位置)的相關(guān)作用域中去找,而不是調(diào)用這個(gè)函數(shù)的那個(gè)作用域中去找。
舉個(gè)栗子就比較好懂些,如下圖

4.函數(shù)的嵌套定義
在JS中,在函數(shù)體中可以再次定義另一個(gè)函數(shù);并且可以多層函數(shù)嵌套使用。
5.作用域鏈
在JS中有兩條鏈,分別是作用域鏈和原型鏈,這里要著重說到作用域鏈。
在函數(shù)的內(nèi)部,要確定一個(gè)變量的值,會(huì)從當(dāng)前的作用域出發(fā),沿著作用域鏈向上找,如果找到全局作用域中還是沒有找到,那么就會(huì)報(bào)引用類型錯(cuò)誤。

再次理解閉包
從閉包的定義可以抓住兩個(gè)關(guān)鍵點(diǎn):
- 函數(shù)嵌套定義
- 引用變量
通過前面的幾個(gè)相關(guān)知識(shí)點(diǎn),可以發(fā)現(xiàn)在JS中,函數(shù)內(nèi)部可以通過作用域鏈機(jī)制輕松得到父級(jí)函數(shù)內(nèi)的變量乃至全局變量,而反過來則是行不通的,即函數(shù)外部是無法讀取函數(shù)內(nèi)局部變量的。
那么如果有的場(chǎng)景必須要得到函數(shù)內(nèi)部的局部變量時(shí),需要變通方法那么就產(chǎn)生了閉包。
可以在本質(zhì)上去理解閉包,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁,可以使局部變量被外部函數(shù)訪問到,也就是說變相的延長了函數(shù)中局部變量的壽命。
上段代碼
function f(){
var a = 1;
function f1(){
console.log(a);
}
return f1;
}
var r = f();
r();
這段代碼執(zhí)行后,結(jié)果輸出為1。也就是說當(dāng)函數(shù)f調(diào)用結(jié)束后,它的局部變量a并沒有被回收掉。可以說這就是閉包的本質(zhì),它使得函數(shù)調(diào)用結(jié)束后,被閉包引用的變量沒有被回收機(jī)制干掉而順利存活了下來,還可以被外部訪問和使用。
閉包的作用
根據(jù)前面的理解,可以歸納閉包的作用有:
- 讀取函數(shù)內(nèi)部的變量
- 延長這些變量的生命周期
使用閉包的栗子來一個(gè) - 節(jié)流函數(shù)
節(jié)流函數(shù),可以讓一個(gè)函數(shù)變得"懶",調(diào)用一次之后需要隔一段時(shí)間才能再次調(diào)用,即降低函數(shù)的可被調(diào)用的頻率。話不多說上代碼

上面的代碼中,f1就是被節(jié)流函數(shù)變懶了的test。
代碼運(yùn)行中,f函數(shù)中t1這個(gè)局部變量被f函數(shù)內(nèi)部定義的t函數(shù)引用后,當(dāng)f函數(shù)調(diào)用執(zhí)行完畢后,t1這個(gè)變量并沒有隨之被回收,而是一直可以被訪問。這就是閉包的體現(xiàn)。
閉包的弊端
- 閉包會(huì)使函數(shù)內(nèi)的變量一直被保存在內(nèi)存中, 這是極耗內(nèi)存的方式。不可以濫用閉包,會(huì)對(duì)網(wǎng)頁的性能有很大的影響。在IE中可能會(huì)導(dǎo)致內(nèi)存泄露。
- 閉包在函數(shù)外部可以訪問并改變函數(shù)內(nèi)部變量的值,這是好事也是壞事。 如果把父級(jí)函數(shù)作為對(duì)象使用,而把閉包作為它的公用方法,而又把其內(nèi)部變量作為它的私有屬性,這時(shí)就一定要注意了,不要輕易改變父級(jí)函數(shù)內(nèi)部變量的值。