1. 程式人生 > >如何理解 JavaScript 閉包

如何理解 JavaScript 閉包

作為 JS 初學者,第一次接觸閉包的概念是因為寫出了類似下面的程式碼:

給列表項迴圈新增事件處理程式。當你點選列表項時不會有任何反應。如何在初學就理解閉包?你需要接著讀下去。

§ 什麼是閉包

說閉包前,你還記得詞法作用域嗎?

執行上面的程式碼打印出 1。

bar 函式是 foo 函式的內部函式,JS 的詞法作用域允許內部函式訪問外部函式的變數。那我們可不可以在外部訪問內部函式的變數呢?理論上不允許。

但是我們可以通過某種方式實現,即將內部函式返回。

內部函式允許訪問其父函式的內部變數,那麼將內部函式返回到出來,它依舊引用著其父函式的內部變數。

這裡就產生了閉包。

簡單來說,可以把閉包理解為函式返回函式

上面的程式碼中,當 increase 函式執行,壓入執行棧,執行完畢返回一個 add 函式的引用,所以 increase 函式內部的變數物件依舊儲存在記憶體中,不會被銷燬。

呼叫 addOne 函式,相當於執行內部函式 add,它可以訪問其父函式的內部變數,從而修改變數 count。而呼叫 addOne 函式所在的環境為全域性作用域,不是定義 add 函式時的函式作用域。

所以,我理解的閉包是一個函式,它在執行時與其定義時所處的詞法作用域不一致,並且具有能夠訪問定義時詞法作用域的能力。MDN 這樣定義:閉包是函式和宣告該函式的詞法環境的組合

§ 閉包的利與弊

◆ 利

第一,閉包可以在函式外部讀取函式內部的變數。

上面這種模式稱為模組模式。我們使用立即執行函式 IIFE 將程式碼私有化但是提供了可訪問的介面,通過公共介面來訪問函式私有的函式和變數。

第二,閉包將內部變數始終儲存在記憶體中。

利用閉包將內部變數(引數)tag 儲存在記憶體中,來封裝自己的型別判斷函式。

◆ 弊

第一,既然閉包會將內部變數一直儲存在記憶體中,如果在程式中大量使用閉包,勢必造成記憶體的洩漏。

在這個例子中,click 事件處理程式就是一個閉包(在這裡是個匿名函式),它將引用著 button 變數;而 button 在這裡本身依舊引用著這個匿名函式。從而產生迴圈引用,造成網頁的效能問題,在 IE 中可能會記憶體洩漏。

解決辦法就是手動解除引用。

第二,如果你將函式作為物件使用,將閉包作為它的方法,應該特別注意不要隨意改動函式的私有屬性。

§ 閉包的經典問題

◆ 迴圈

現在我們來解決一下文章開頭出現的問題。

額外宣告一個 makeHelpCallBack 的函式,將迴圈每次的上下文環境通過閉包儲存起來。

◆ setTimeout

結果為 1 秒後,列印 5 個 5。

我們可以利用閉包保留詞法作用域的特點,來修改程式碼達到目的。

結果為 1 秒後,依次列印 0 1 2 3 4。

§ 小結

閉包在 JS 中隨處可見。

閉包是 JS 中的精華部分,理解它需要具備一定的作用域、執行棧的知識。理解它你將收穫巨大,你會在 JS 學習的道路上走得更遠,比如會在後面的文章來討論高階函式和柯里化的問題。

◆ 文章參考

閉包 | MDN

學習 JavaScript 閉包 | 阮一峰

Understanding JavaScript Closures: A practical Approach | Paul Upendo

閉包造成問題洩漏的解決辦法 | CSDN

最後:“相信有很多想學前端的小夥伴,今年年初我花了一個月整理了一份最適合2018年學習的web前端乾貨,從最基礎的HTML+CSS+JS到移動端HTML5等都有整理,送給每一位前端小夥伴,53763,1707這裡是小白聚集地,歡迎初學和進階中的小夥伴。”

祝大家早日學有所成,拿到滿意offer,快速升職加薪,走上人生巔峰。