Skip to main content

JavaScript Closure 筆記

什麼是 Closure

在函數裡面所建立的變數,在函數執行完畢後就會釋放清空這些變數的記憶體,但在一些特殊情況下,函數在執行完後,裡面的變數還會繼續「被記得」

function pocket() {
let money = 0;
function increaseMoney() {
money += 100;
console.log(money);
}
return increaseMoney;
}

myPocket = pocket();
myPocket(); // 100
myPocket(); // 200

pocket 會回傳一個新的 function increaseMoney,這 function 會對 money 變數進行增加,並且印出來

  1. increaseMoney 需要藉由 scope chain 到外層作用域查找到 money 變數
  2. pocket 執行完畢回傳 increaseMoney理應 pocket 內部的變數記憶體都該釋放掉(但是沒有)
  3. 因為 increaseMoney 被回傳出來後,後續運用還是需要存取到外層的 money,一旦這種參考關連建立起來, money 的記憶體在 pocket 執行完畢後並不會被釋放掉
  4. 從外部也不能直接存取 money,這樣會達到類似私有變數的效果

Closure 特點

  • 閉包的優點是 變數僅存在於函式之中,如果匯出的 method 不能存取原始值,將無法用任何方式取得原始變數,換句話說,可以限制開發者用自定的安全方法來操作函式內的變數
  • 每個閉包之間都是獨立的
function pocket(name) {
let money = 0;
return function () {
money += 100;
console.log(`${name}, 目前有 ${money}`);
};
}

let increaseAlexPocket = pocket("Alex");
let increaseBobPocket = pocket("Bob");

increaseAlexPocket();
// Alex, 目前有 100
increaseBobPocket();
increaseBobPocket();
increaseBobPocket();
// Bob, 目前有 300

如何清除 Closure

increaseAlexPocket = null;

這樣就可以清除函數以及內部的變數記憶體了

Closure 精確定義

mdn: 閉包(Closure)是函式以及該函式被宣告時所在的作用域環境(lexical environment)的組合

換句話說,閉包指的是:所有函數可以捕捉被建立時的作用域環境(例如變數),只不過上面的範例才會真正體現實作所關心的閉包性質

當內部 (inner) 函式被建立後,除了自己本身的程式碼外,也可以取得該內部函式外層環境的變數值,進而記住了執行當時的環境,這就是「閉包」

Closure 記住值,是複製還是參照(refer)?

是參照記憶體位置,詳見

https://javascript.info/closure#garbage-collection

Usually, a Lexical Environment is removed from memory with all the variables after the function call finishes. That’s because there are no references to it. As any JavaScript object, it’s only kept in memory while it’s reachable.

However, if there’s a nested function that is still reachable after the end of a function, then it has [Environment](Environment) property that references the lexical environment.

In that case the Lexical Environment is still reachable even after the completion of the function, so it stays alive.

應用 : For Loop

var btn = document.querySelectorAll("button");
for (var i = 0; i <= 4; i++) {
btn[i].addEventListener("click", function () {
alert(i);
});
}

每個按鈕都會是 5,因為 callback 會取到跑完迴圈且值為 5 的 global variable i

for (let i = 0; i <= 4; i++) {
btn[i].addEventListener("click", function () {
alert(i);
});
}

使用 let 就會個別按鈕分別彈出 0, 1, 2, 3, 4,因為使用 let,每一次迴圈的 i 都是獨立的塊級作用域,而 callback function 建立時,需要會去向外層查找到 i,閉包就此建立

應用 : Currying

// 多參數形式
function multiply(a, b, c) {
return a * b * c;
}

// currying 形式
function multiplyCurry(a) {
return (b) => {
return (c) => {
return a * b * c;
// a, b 藉由 closure 性質被記住
};
};
}

multiply(1, 2, 3); // 6
multiplyCurry(1)(2)(3); // 6

Closure 的注意事項

Closure 會搜尋作用域跟 scope chain 會比較消耗效能,也會鎖住函數內部的變數的記憶體,所以要小心記憶體運用

Reference

所有的函式都是閉包:談 JS 中的作用域與 Closure

Variable scope, closure

為什麼我們需要閉包(Closure)?它是冷知識還是真有用途?

閉包,原來這就是閉包啊!

[JS] 深入淺出 JavaScript 閉包(closure)

Closure 閉包