閉包算是在 JS 中常聽到,卻不容易使用的一個方法,更多狀況是不小心用出來,~~因此出 bug ~~
在介紹閉包之前,先來看看下面範例:
function randomString(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
function getData() {
var demoData = [];
for (let i = 0; i < 1000; i++) {
demoData.push(randomString(1000))
}
}
getData();
randomString
是一個會根據設定執行數次,產生亂碼字串的方法,而這亂碼字串會藉由 return result
回傳,最後又在 push 至 demoData 變數上。
使用 chrome 無痕模式來觀察 Memory ,可以發現在執行上述程式碼時,記憶體使用了 1.3 MB
再來執行另外一段非常相近的程式碼,來看看他的記憶體使用量:
function randomString(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
var demoData = [];
function getData() {
for (let i = 0; i < 1000; i++) {
demoData.push(randomString(1000))
}
}
getData();
可以發現這個範例記憶體增使用量將近 21MB ,這是因為第二個範例 demoData
變數是被放在外層也就是全域( window)底下,而 demoData
變數此時是能在被呼叫、使用的。
而第一個範例中, demoData
是在函示裡頭, demoData
變數則會跟著 getData
執行完畢時,一同被釋放記憶體,此時 demoData
也是無法被呼叫的,因此兩者記憶體落差十分大。
之所以要先講這一段是因為,記憶體可以說是閉包的一個重點。
接下先來看看閉包的一個簡單範例
function openFn() {
let num = 10
function ClosureFn(newNum) {
num = num + newNum
return num
}
//函示內 return 函示就會變成 『閉包』
return ClosureFn
}
const useClosure = openFn()
console.log(useClosure(10)) //20
console.log(useClosure(10)) //30
console.log(useClosure(10)) //40
在上面範例中 ClosureFn
其實就是閉包,若要執行這個閉包可以直接使用 openFn()(100)
其就會被執行,不過一般來說我們不會直接使用兩個 ()()
小刮號做執行,而是像上面範例中使用 openFn()
並且再用一個變數來做指向。
而上面有提到記憶體是閉包的重點,關於這一點我們可以看看連續執行 useClosure()
後回傳的值會不斷疊加,然而 ClosureFn
閉包函示內部雖然沒有 let num = 100
,不過閉包內部會因為 num = num + newNum
這段程式碼,有使用到 num
變數,因此按照作用域的規則,會訪問(參考)外層函式的 let num
變數,因為這個訪問(參考)的動作,就會讓 num
變數的記憶體『不被釋放』,因此當正是因為這個『不被釋放』,我們使用 useClosure(10)
的值才可以不斷被疊加。
這邊也試者使用圖片來增加對閉包的理解:
之所以要使用閉包,就是因為可以透過不同變數、常數,讓閉包回傳資料各自獨立,某些需要重複使用程式碼的狀況就可以使用閉包,例如
function openFn() {
let num = 10
function ClosureFn(newNum) {
num = num + newNum
return num
}
//函示內 return 函示就會變成 『閉包』
return ClosureFn
}
const useClosure1 = openFn()
console.log(useClosure1(10))
console.log(useClosure1(10))
const useClosure2 = openFn()
console.log(useClosure2(100))
console.log(useClosure2(100))
下篇則會介紹閉包延伸運用。