1. 程式人生 > 前端設計 >閉包到底是什麼?

閉包到底是什麼?

前言

閉包閉包,閉包是JavaScript最強大的特性,沒有之一,很多強大JavaScript庫比如jQuery、Vue.js都使用了閉包的特性來實現的。閉包幾乎是一線網際網路企業面試必問的題

如何理解閉包?

每個人的理解都不一樣,我用深和淺來概括一下。

  • 深: 函式執行形成無法釋放的上下文棧記憶體
  • 淺: 函式執行後使其內部變數能夠被外界訪問

閉包經典理解

  • 由於var 變數的提升,迴圈的時候賦值都是用一個i;
  • 因為setTimeout為巨集任務,由於JS中單執行緒eventLoop機制,在主執行緒同步任務執行完後才去執行巨集任務,因此迴圈結束後 才執行setTimeout,但輸出i的時候當前作用域沒有,往上一級再找,發現了i,此時迴圈已經結束,i變成了10;
  • 使用let可以解決 let形成塊級作用域不會提升
for (var i = 0; i < 10; i++) {
    //這個時候 i已經 = 10了  var i提升 每次進行賦值都是同一個i;
    setTimeout(() => {
        console.log(i);// 列印10個10
    },1000)
}
複製程式碼

使用閉包

for (var i = 0; i < 10; i++) {
    ((i) => {
        setTimeout(() => {
            console.log(i);//列印0-9
        },1000
) })(i) } 複製程式碼
  • 和let一個效果?那麼可不可以認為閉包是一種作用域,它拷貝了一套外層函式作用域中被訪問的引數、變數/函式,這個拷貝都是淺拷貝?大家踴躍討論我是個小白x.x

閉包具體步驟(沖沖衝)

  • 建立函式時
    • 開闢一個堆記憶體
    • 把函式體中的程式碼當作字串存進去
    • 把堆記憶體的地址賦值給函式名/變數名
    • 函式在哪建立,那麼他指向時所需要查詢的上級作用域就是誰
  • 函式指向
    • 形成一個全新的私有作用域,執行上下文,私有棧記憶體 ( 執行一個形成一個,多個自己也不會產生影響 )
    • 形參賦值,變數提升
    • 程式碼執行(把所屬堆記憶體中的字串拿出來一行行執行) 遇到變數,會依次向他的私有作用域中或者上級作用域(最頂到window)尋找; 私有變數和全域性其他的變數沒有必然關係,閉包保護機制 3.函式執行
    • 通過執行上下文棧執行入棧出棧過程

無法釋放的棧記憶體

  • 正常情況下,函式執行完上下文銷燬然後出棧即完畢
  • 非正常情況下,當前上下文的變數物件被外界所引用,那麼這個棧記憶體會一直存在且不會被釋放

閉包有什麼作用?

  • 變數長期駐紮在記憶體中
  • 避免全域性變數的汙染
  • 私有成員的存在
  • 模組化封裝,以及HOC等

上面的這些特點,促使了許多庫的誕生,比如JQuery,將程式碼都寫在閉包中,只暴露出$出來供我們呼叫內部的方法

閉包的缺點

javascript中的垃圾回收(GC)規則是這樣的:如果物件不再被引用,或者物件互相引用形成資料孤島後且沒有被孤島之外的其他物件引用,那麼這些物件將會被JS引擎的垃圾回收器回收;反之,這些物件一直會儲存在記憶體中。

  • 由於閉包會引用包含它的外層函式作用域裡的變數/函式,因此會比其他非閉包形式的函式佔用更多記憶體。
  • 當外層函式執行完畢退出函式呼叫棧(call stack)的時候,外層函式作用域裡變數因為被引用著,可能並不會被JS引擎的垃圾回收器回收,因而會引起記憶體洩漏。過度使用閉包,會導致記憶體佔用過多,甚至記憶體洩漏。