閉包與記憶體:為什麼會洩漏?
閉包 = 函式 + 它記住的外部變數
function createCounter() {
let count = 0; // ← 這個變數被閉包「抓住」了
return () => ++count;
}
const counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3
// count 不會被垃圾回收,因為 counter 還在引用它
為什麼會記憶體洩漏?
陷阱 1:事件監聽器沒移除
function setupButton() {
const data = new Array(1000000).fill('x'); // 大量資料
document.getElementById('btn').addEventListener('click', () => {
console.log(data.length); // 閉包引用了 data
});
}
setupButton();
// data 永遠不會被回收,因為事件監聽器的閉包引用著它
// 即使你不再需要 data,它也一直佔著記憶體
// ✅ 修復:移除監聽器
const handler = () => console.log('clicked');
btn.addEventListener('click', handler);
// 不需要時
btn.removeEventListener('click', handler);
陷阱 2:定時器沒清除
function startPolling() {
const hugeData = fetchSomething();
setInterval(() => {
process(hugeData); // 閉包抓住 hugeData
}, 1000);
}
// setInterval 永遠不會停止 → hugeData 永遠不會被回收
// ✅ 修復
const intervalId = setInterval(...);
clearInterval(intervalId); // 不需要時清除
陷阱 3:DOM 引用
function setup() {
const element = document.getElementById('modal');
element.addEventListener('click', () => {
element.style.display = 'none'; // 閉包引用 element
});
// 即使 modal 從 DOM 移除,JS 記憶體中還是有引用 → 不會被回收
}
WeakMap / WeakRef
// WeakMap 的 key 是弱引用,不阻止垃圾回收
const cache = new WeakMap();
let obj = { name: '小明' };
cache.set(obj, 'cached data');
obj = null; // obj 被回收 → WeakMap 中的 entry 也自動消失
// 不會造成記憶體洩漏!
如何檢測記憶體洩漏?
Chrome DevTools → Memory 分頁
1. 拍第一張 Heap Snapshot
2. 執行可疑的操作
3. 拍第二張 Heap Snapshot
4. 比較差異 → 看哪些物件增加了
閉包本身不是壞事(它是 JS 的核心特性),問題是忘了清理不再需要的引用。