JavaScript内存泄漏排查:heap snapshot分析
问题复现
最近项目中遇到一个严重问题:页面在长时间使用后会越来越卡顿,最终出现页面假死现象。通过Chrome DevTools的Heap Snapshot分析发现,DOM节点和闭包引用持续增长。
复现步骤:
- 创建一个包含大量动态列表的页面(5000+条数据)
- 每个列表项都绑定事件处理器并创建闭包引用
- 不断添加删除列表项,但未正确清理事件监听器
- 使用Performance Monitor观察内存使用情况
核心问题分析
通过heap snapshot发现以下问题:
- 1500+个DOM节点被意外保留(未被垃圾回收)
- 800+个闭包引用持续存在
- 事件监听器未正确移除导致内存泄漏
实际代码示例
// ❌ 存在内存泄漏的代码
function createList() {
const container = document.getElementById('list');
for (let i = 0; i < 5000; i++) {
const item = document.createElement('div');
item.textContent = `Item ${i}`;
// 绑定事件但未清理
item.addEventListener('click', function() {
// 闭包引用了外部变量
console.log(`clicked item ${i}`);
});
container.appendChild(item);
}
}
优化策略
1. 正确清理事件监听器
// ✅ 优化后代码
function createList() {
const container = document.getElementById('list');
const listeners = [];
for (let i = 0; i < 5000; i++) {
const item = document.createElement('div');
item.textContent = `Item ${i}`;
const handler = function() {
console.log(`clicked item ${i}`);
};
item.addEventListener('click', handler);
listeners.push({element: item, handler});
container.appendChild(item);
}
// 清理函数
return () => {
listeners.forEach(({element, handler}) => {
element.removeEventListener('click', handler);
});
};
}
2. 使用WeakMap避免循环引用
const eventMap = new WeakMap();
function attachListener(element) {
const handler = function() {
console.log('clicked');
};
element.addEventListener('click', handler);
eventMap.set(element, handler);
}
性能对比数据
优化前:内存使用持续增长,10分钟后达到250MB 优化后:内存稳定在80MB,无明显增长趋势
通过heap snapshot分析,我们发现内存泄漏主要来源于未清理的DOM引用和闭包,建议定期进行性能检测和内存分析。

讨论