什麼是閉包?
通常情況下,function內部的變數在執行完後就會被釋放,但閉包阻止了這件事,閉包模式可以讓變數駐留在記憶體中不被回收,這是普通的函數不具備的。
NOTE相關定義:
- 避免變數全局汙染。
- 數據私有化,外部無法修改。
- 可以讓外部使用內部的私有數據。
C1 普通的function寫法:
let a = 10;function fn(){ a++; console.log(a);}fn();fn();fn();
// output = 11、12、13C2 變數寫在function內的寫法:
let a = 10;function fn(){
let a = 20; a++; console.log(a);}fn();fn();fn();
o嗯? 第二個的範例不也是一般的函數嗎? 為什麼輸出結果是文章內所提及的閉包模式特性?
沒錯,避免變數的全局汙染以及數據私有化這完全就是function在做的事情,以及讓外部使用內部的私有數據這也只是return返回數據的操作。到這個階段必須反思,如果僅僅是因為這些需求,根本就不需要用到閉包,平時使用函數已經是在做相同的作業。
什麼樣的情況才是閉包需求?
先回顧閉包的核心作用讓變數可以駐留在記憶體中,不被回收,接續用C2的範例修改成閉包模式,讓目前的變數a可以像普通函數一樣遞增下去變成21、22、23,再拿一個變數把這個回傳的函數接住。
let a = 10;function fn(){ let a = 20; return function(){ // 在這個函數裡面嵌套一個新的函數並回傳(匿名函數也可以) a++; console.log(a); }}let f = fn(); // 保存回傳的內部函數,使其形成閉包並記住當時的作用域變數然後打開瀏覽器的開發者工具(F12/右鍵->檢查)在宣告變數位置增加斷點(Breakpoint)並刷新來源。

能看見作用域(Scopes)中有Clourse出現,現在裡面有這個變數存在,它的初始值是20,正式完成了閉包的作業,最後輸出看看結果是否有達到預期的遞增。
let a = 10;function fn(){ let a = 20; return function(){ a++; console.log(a); }}let f = fn();
f();f();f();
o輸出結果有正確遞增的話,代表現在這個閉包有正確駐留變數在記憶體中,但如果是像普通的函數一樣執行完後並正常釋放,那麼這就不是閉包,它理應要駐留在記憶體中沒有被回收走才是正確的閉包模式。
IMPORTANT嵌套的function必須使用到外層函數變數,如果內層沒有使用到外層的函數變數,那麼就不會形成閉包。
閉包會有memory leak嗎?
既然我們認識了閉包的核心功能,這是否代表會有memory leak(記憶體洩漏)的問題? 沒錯哦,按照C2的範例,現在變數a已經是記憶體洩漏的狀態,數據長時間留在記憶體內沒有被回收釋放,解決方法也很簡單,只需要把f函數結果指向null讓它不再引用就可以被回收走。
let a = 10;function fn(){ let a = 20; return function(){ a++; console.log(a); }}let f = fn();f();f();f();
f = null;不過有些記憶體洩漏情況是允許的,舉例初始化階段常駐在全域的快取或設定資料…等,甚至是刻意常駐在記憶體當中,這個視專案狀況而定。記憶體就是拿來佔用的,不是不能用多,是不能失控。
