JavaScript 闭包内存泄漏避免指南
JavaScript 闭包会让对象在内存中存活,只要闭包函数还有引用。当对象不再需要时,如果被闭包捕获,垃圾回收器就无法释放它,导致内存泄漏。
来看一个典型例子,涉及大对象:
let bigData = {
id: 42,
data: new Array(1000000).fill('Some data')
}
function handler(data) {
return function() {
console.log(data.id);
debugger;
}
}
const runHandler = handler(bigData);
bigData = null;
runHandler();
内部函数只用了 data.id,但闭包捕获了整个 bigData 对象。在 DevTools 断点处,你会看到对象仍滞留在内存中。
闭包中使用 eval() 的问题
eval() 函数会加剧泄漏,因为引擎会把所有外部变量加入闭包,无论是否使用:
let hello = 'Hello';
let userName = 'Vasya';
setTimeout(function () {
debugger;
return eval('console.log("Hello from Vasya!")');
}, 1000);
在 DevTools 中,闭包包含了 hello 和 userName,尽管 eval() 没引用它们。这是由于 eval() 内代码的不确定性。生产环境中避免使用 eval()。
防止内存泄漏的方法
强制重置数据
使用后重置捕获的数据:
function runCalc(buttonId) {
let bigData = new Array(1000000).fill('Something');
const button = document.getElementById(buttonId);
button.addEventListener('click', function onClick() {
console.log('Processed items:', bigData.length);
bigData = null;
});
}
将 bigData 赋值为 null 释放闭包内的引用,让垃圾回收器移除对象。
正确移除事件处理器
移除 DOM 元素时,事件处理器是常见泄漏源:
function clickSetup(btn, message) {
const bigData = new Array(1000000).fill(message);
function handler() {
console.log(message, bigData.length);
btn.remove();
btn.removeEventListener('click', handler);
}
btn.addEventListener('click', handler);
}
命名函数 handler 可以随元素一起移除。匿名箭头函数无法这样移除。
WeakMap 实现弱引用
WeakMap 通过对对象键的弱引用,解决捕获大对象的问题。
键必须是对象
const weak = new WeakMap();
const key = {};
weak.set(key, 'Vasya was here!');
console.log(weak.get(key));
const anotherKey = {};
console.log(weak.get(anotherKey)); // undefined
键必须是对象或非原始类型。通过引用访问 get(key)。
弱引用与垃圾回收
WeakMap 中的引用不会阻止垃圾回收:
const weak = new WeakMap();
let userKey1 = {id: 1};
weak.set(userKey1, 'Vasya');
userKey1 = null;
将 userKey1 置为 null 后,对象和值会被垃圾回收。
无迭代器
没有 size、forEach、keys()。仅基本方法:
set(key, value)— 设置get(key)— 获取has(key)— 检查delete(key)— 删除
无迭代器避免垃圾回收时的不可预测性。
在闭包中使用 WeakMap
使用 WeakMap 的闭包不会强持有对象:
function createProcessor() {
const weakMap = new WeakMap();
return function process(obj) {
weakMap.set(obj, obj.value);
debugger;
return weakMap.get(obj);
}
}
const processor = createProcessor();
let bigData = {
value: 21,
hugeArray: new Array(1000000).fill('*')
};
console.log(processor(bigData));
bigData = null;
let bigData2 = {
value: 42,
hugeArray: new Array(1000000).fill('1')
};
console.log(processor(bigData2));
垃圾回收后 DevTools 中只剩 bigData2。WeakMap 实现自动内存清理。
验证步骤:
- 在
process()中设置断点。 - 单步执行代码 (F10)。
bigData = null后,切换到 Memory 标签。- 点击按钮触发垃圾回收。
- 继续执行 — 只剩当前键。
关键要点
- 闭包捕获整个对象,即使只用一个属性。
eval()将所有外部变量加入闭包。- 命名事件处理器可通过
removeEventListener移除。 WeakMap对对象键使用弱引用。WeakMap无迭代器,确保垃圾回收可预测。
— Editorial Team
暂无评论。