2024-06-17
#JavaScriptJs Closure 閉包
#什麼是閉包(Closure)
我們先來看一個範例
fn
明明是在最外層作用域執行的,但它卻可以得到在 outer
作用域裡的 x,這就是因為有閉包。
function outer() {
let x = 10;
// 這邊形成了閉包
function inner() {
console.log(x);
}
return inner;
}
const fn = outer();
fn(); // 10
根據 MND 定義:
閉包(Closure)是函式以及該函式被宣告時所在的語法作用域(lexical environment)的組合。
(語法作用域指的是變數的可見性和可訪問性是語法解析時就決定的,而不是由呼叫它們的位置決定。)
在這個例子中,閉包就是函式 inner
以及它被宣告時所在的作用域(也就是 outer
內部)的組合。
簡單來說,閉包的函式可以訪問、記住它被創建時的外部作用域的變數,就算它不是在這個作用域中執行也可以。
#閉包的應用
1. 模擬私有變數
我們可以利用閉包用來創建私有變數,把不想外露的變數限制在閉包內部使用,只有通過閉包函數內的方法才能讀取和修改它們。
如以下程式碼:
- 變數 count 定義在函式內,無法直接被外部讀取
- 需透過
getCount
方法才能得到 count 的值 - 只能透過
increment
和decrement
來改變 count
function createCounter() {
// count 無法被外部讀取
let count = 0;
return {
increment: function () {
count++;
},
decrement: function () {
count--;
},
getCount: function () {
return count;
},
};
}
const counter = createCounter();
console.log(counter.getCount()); // 0
counter.increment();
counter.increment();
console.log(counter.getCount()); // 2
counter.decrement();
console.log(counter.getCount()); // 1
console.log(count); // Uncaught ReferenceError: count is not defined
2. 緩存
我們也可以使用閉包來實現簡單的緩存機制,將計算結果存在閉包中,避免重複、耗時的運算。
// 一個耗時的運算
function fibonacci(n) {
if (n <= 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
function createCache(func) {
// 聲明一個 cache 物件,在物件裡放緩存的東西
const cache = {};
return function (num) {
if (cache[num]) {
// 若已有緩存,就直接使用緩存的結果
return cache[num];
} else {
// 若沒有緩存,就算出結果並放入緩存
const value = func(num);
cache[num] = value;
return value;
}
};
}
const compute = createCache(fibonacci);
console.log(compute(5)); // 印出 5
console.log(compute(40)); // 需等待一陣子才印出 102334155
console.log(compute(40)); // 不需等待,直接印出 102334155
3. 異步操作
閉包在異步操作情境下非常有用,因為閉包讓函式能夠記住、訪問它被宣告時的作用域環境變數。
如以下範例:
在 seTimeout
裡的回呼函式形成了一個閉包,記住了它外部作用域的 name 變數。
即使 delayedGreeting
已經執行完畢了,回呼函式仍然可以訪問並使用 name 變數,因此一秒後能正確印出 Hello, Iris!
文字。
function delayedGreeting(name) {
setTimeout(() => {
console.log('Hello, ' + name + '!');
}, 1000);
}
delayedGreeting('Iris');
#閉包缺點
內存洩漏 (memory leak)
閉包的特性同時也造成它的缺點,閉包會讓函式記得它作用域環境的變數,因次局部變數不會釋放,如果使用過多、沒有清除可能會造成內存洩漏,需要小心使用。
function outer() {
// longArray 並沒被使用到,但一直被記著,記憶體沒有釋放
const longArray = [];
return function inner(num) {
longArray.push(num);
};
}
const addNumbers = outer();
for (let i = 0; i < 100000000; i++) {
addNumbers(i);
}
參考資料: