Day.26 「閉包要謹慎使用!」 —— JavaScript 閉包(Closure)

我們前面已經認識了函式作用域,也瞭解了回調函式,但有時候會產生意想不到的事情,造成內存問題,其中一個是閉包
認識閉包
閉包是如何產生的?
閉包通常出現在巢狀函式中,是內部函式使用了外部函式的變數時,產生閉包!
閉包是什麼?
首先我們用簡單的範例
function fn() {
let a = 1
function plus1() {
a++
console.log(a)
}
return plus1
}
const f = fn()
f()我們透過 Google Chrome 的開發人員工具查看執行過程

可以看到在執行到第 8 行的時候,產生了閉包,也就是紅框處!
由此可見~閉包是在我們內部函式使用到了外部函式的變數時產生出來。 你可能想說第 9 行函式還沒執行呀!? 那是因為,函式的提升,所以導致執行到函式變數的時候就產生閉包。 而這時有被內部函式使用的變數,就會存在閉包之中!
常見的閉包
內部函式 為 外部函式 的返回值
就是我們上面的範例
function fn() {
let a = 1
function plus1() {
a++
console.log(a)
}
return plus1
}
const f = fn()
f() // 2
f() // 3你會發現!函式內的變數 a 還存在可以累加並沒有消失,而這個值就存在 f 函式的閉包中!
回調函式
沒錯!回調函式也會產生閉包~如下面範例
function consoleDelay(msg, time) {
setTimeout(() => {
console.log(msg)
}, time)
}
consoleDelay('我也是閉包', 2000)一樣有形成閉包的條件!內部函式引用了外部函式的變數!

閉包的作用
-
一般函式執行完畢後,就會從內存釋放,而閉包則是把函式內的變數,繼續保留在內存中(延長了局部變數的生命週期)。
-
一般函式內的變數無法從外部操作,但閉包可以間接操控函式內部的變數值。
閉包的優點與缺點
閉包的優點同時也是缺點
-
函式執行完後,函式內的局部變數不會釋放,如果這個局部變數還會使用,那就是優點,如果不會使用了,那就變成缺點,因為佔用內存的時間會變長。
function fn() { const arr = Array.from({ length: 10000 }) function arrLength() { console.log(`內存有 ${arr.length} 長度的陣列`) } return arrLength } const f = fn() f() // 內存有 10000 長度的陣列 -
容易造成內存溢出與洩漏
- 內存溢出比較簡單理解,就是內存不夠跑程式而報錯

- 內存洩漏平常還可以正常執行,但每天洩漏一點,會把內存空間壓縮,更容易導致內存溢出,常見的內存洩漏
- 意外使用了全局變數(宣告習慣很重要),如:函式內忘記使用宣告,直接使用變數
- 沒有及時清理的計時器或回調函式
- 閉包
- 內存溢出比較簡單理解,就是內存不夠跑程式而報錯
如何解決
-
盡量避免濫用閉包
-
及時釋放
f = null // 讓內部函式變成「垃圾物件」,瀏覽器會自動清除
總結
我們已經逐步學習 JavaScript 的核心精隨了!也是面試很長考的觀念~
相關文章
Day.30 「什麼!? Promise 的語法糖?」 —— ES8 Async & Await
ES8 新增了 async 與 await 兩個語法,這兩個語法也是為了更方便解決非同步的語法,可以說是 Promise then 的語法糖。
Day.29 「Class 語法糖~」 —— ES6 Class 構造函式
ES6 新增了更接近傳統語言寫法的 Class 這個概念,基本上可以當作是一個**語法糖**,絕大部分的功能在 ES5 都可以做到,但 Class 可以讓構造函式的寫法更加清新!
Day.28 「Promise 初體驗~」 —— ES6 Promise
我們前面已經學習了回調函式(Callback Function)與構造函式(Constrcutor),而 Promise 是 ES6 新增用來解決非同步回調地域的新語法,同時也是一個構造函式!