閉包(Closure)概念
在A函數(shù)中定義了一個B函數(shù)亭畜,在B函數(shù)中使用了A函數(shù)中的變量倦沧,就會產(chǎn)生閉包,其中B就是一個閉包墩弯。
也可以說省骂,定義在一個函數(shù)內(nèi)部的這個函數(shù)就是閉包。
理解閉包需要的幾個相關概念
1.變量的作用域
在ES5中變量有兩個作用域:
- 全局作用域(global)
- 局部作用域(local)
在ES6中補充了一個新的作用域-塊級作用域(block)
當定義變量的地方?jīng)]有被 function 包括則是全局變量最住,否則就是局部變量钞澳。
在函數(shù)的內(nèi)部會優(yōu)先使用局部變量(即使全局變量和局部變量同名亦是如此),在函數(shù)調(diào)用的過程涨缚,會給局部變量創(chuàng)建一個函數(shù)棧區(qū)(區(qū)別于全局棧)策治,來保存這些局部變量。正常情況下在函數(shù)調(diào)用結束后兰吟,函數(shù)棧會被垃圾回收機制處理通惫。
2.執(zhí)行上下文
- 代碼的運行會產(chǎn)生執(zhí)行上下文,如果代碼不運行就沒有。
- 全局代碼產(chǎn)生全局上下文
- 函數(shù)代碼產(chǎn)生函數(shù)上下文
- 函數(shù)嵌套調(diào)用形成執(zhí)行上下文棧
- 執(zhí)行上下文中保存了執(zhí)行代碼所需要的各類的數(shù)據(jù)
- 執(zhí)行上下文是一個對象,在代碼運行過程中產(chǎn)生,代碼運行完成后就消失.
執(zhí)行上下文的組成
- 自己的執(zhí)行上下文
- 父級函數(shù)的執(zhí)行上下文
全局上下文
一旦<script></script>
標簽中代碼運行起來,就會產(chǎn)生一個執(zhí)行上下文,這個執(zhí)行上下文就是全局執(zhí)行上下文.
- 全局上下文只有一個
- 全局執(zhí)行環(huán)境是window對象,所有變量和函數(shù)都作為window對象的屬性和方法創(chuàng)建的
- 所有的代碼都在全局執(zhí)行上下文中執(zhí)行
函數(shù)執(zhí)行上下文
每次調(diào)用函數(shù)都會產(chǎn)生一個執(zhí)行上下文混蔼,函數(shù)調(diào)用完成后履腋,會把執(zhí)行上下文釋放掉。
按照函數(shù)的調(diào)用順序惭嚣,這些上下文以棧的形式存儲(先進后出).棧底是全局上下文遵湖。
3.詞法作用域-靜態(tài)作用域
js代碼的書寫順序,就決定了變量的作用域晚吞。換言之延旧,在函數(shù)內(nèi)部去訪問一個變量,應該去定義這個函數(shù)(寫這個函數(shù)的位置)的相關作用域中去找槽地,而不是調(diào)用這個函數(shù)的那個作用域中去找迁沫。
舉個栗子就比較好懂些,如下圖
4.函數(shù)的嵌套定義
在JS中,在函數(shù)體中可以再次定義另一個函數(shù);并且可以多層函數(shù)嵌套使用捌蚊。
5.作用域鏈
在JS中有兩條鏈集畅,分別是作用域鏈和原型鏈,這里要著重說到作用域鏈缅糟。
在函數(shù)的內(nèi)部牡整,要確定一個變量的值,會從當前的作用域出發(fā)溺拱,沿著作用域鏈向上找逃贝,如果找到全局作用域中還是沒有找到,那么就會報引用類型錯誤迫摔。
再次理解閉包
從閉包的定義可以抓住兩個關鍵點:
- 函數(shù)嵌套定義
- 引用變量
通過前面的幾個相關知識點沐扳,可以發(fā)現(xiàn)在JS中,函數(shù)內(nèi)部可以通過作用域鏈機制輕松得到父級函數(shù)內(nèi)的變量乃至全局變量句占,而反過來則是行不通的沪摄,即函數(shù)外部是無法讀取函數(shù)內(nèi)局部變量的。
那么如果有的場景必須要得到函數(shù)內(nèi)部的局部變量時纱烘,需要變通方法那么就產(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í)行后,結果輸出為1哺壶。也就是說當函數(shù)f調(diào)用結束后屋吨,它的局部變量a并沒有被回收掉蜒谤。可以說這就是閉包的本質(zhì)至扰,它使得函數(shù)調(diào)用結束后鳍徽,被閉包引用的變量沒有被回收機制干掉而順利存活了下來,還可以被外部訪問和使用敢课。
閉包的作用
根據(jù)前面的理解,可以歸納閉包的作用有:
- 讀取函數(shù)內(nèi)部的變量
- 延長這些變量的生命周期
使用閉包的栗子來一個 - 節(jié)流函數(shù)
節(jié)流函數(shù)阶祭,可以讓一個函數(shù)變得"懶",調(diào)用一次之后需要隔一段時間才能再次調(diào)用直秆,即降低函數(shù)的可被調(diào)用的頻率濒募。話不多說上代碼
上面的代碼中,f1就是被節(jié)流函數(shù)變懶了的test切厘。
代碼運行中萨咳,f函數(shù)中t1這個局部變量被f函數(shù)內(nèi)部定義的t函數(shù)引用后懊缺,當f函數(shù)調(diào)用執(zhí)行完畢后疫稿,t1這個變量并沒有隨之被回收,而是一直可以被訪問鹃两。這就是閉包的體現(xiàn)遗座。
閉包的弊端
- 閉包會使函數(shù)內(nèi)的變量一直被保存在內(nèi)存中, 這是極耗內(nèi)存的方式。不可以濫用閉包俊扳,會對網(wǎng)頁的性能有很大的影響途蒋。在IE中可能會導致內(nèi)存泄露。
- 閉包在函數(shù)外部可以訪問并改變函數(shù)內(nèi)部變量的值馋记,這是好事也是壞事号坡。 如果把父級函數(shù)作為對象使用,而把閉包作為它的公用方法梯醒,而又把其內(nèi)部變量作為它的私有屬性宽堆,這時就一定要注意了,不要輕易改變父級函數(shù)內(nèi)部變量的值茸习。