Chrome DevTools 内存快照里怎么定位闭包导致的内存泄漏?
我在做一个单页应用,发现切换页面后内存一直涨,用 DevTools 的 Memory 面板拍了快照,看到很多 Detached DOM tree 和 Closure,但不知道具体是哪段代码引起的。我怀疑是事件监听没清理,但试过 removeEventListener 也没用。
比如下面这个组件,每次进入页面都会重新创建,但退出后好像还被引用着:
function createWidget(container) {
let data = fetchHugeData(); // 返回大量数据
const handleClick = () => {
console.log(data.length); // 闭包引用了 data
};
container.addEventListener('click', handleClick);
return {
destroy() {
// 忘了移除监听器...
}
};
}
这种情况下,怎么在 DevTools 里快速找到是哪个闭包持有了 data 不放?
操作步骤是这样的:
先进入你的页面,让组件初始化,在 Memory 面板拍个 Heap Snapshot(快照A)。然后退出页面,触发一下 GC(DevTools 里那个带扫把的圆圈图标),再拍第二个快照(快照B)。
重点来了,选中快照B,把左侧视图从 Summary 切换成 Comparison,对比基准选快照A。这时候看 Delta 列,找那些正数且内存占用大的对象,比如你那个
data数组,肯定排在前面。点进去这个对象,看下方的 Retainers 面板,这里就是案发现场。你会看到一条引用链,通常长这样:某个 DOM 节点 -> 事件监听器 -> 闭包上下文 -> 你的
data变量。如果 DOM 节点是 Detached DOM tree,那就说明 DOM 没了,但事件监听器还活着,它死死抓着闭包不放。至于代码修复,你那个
destroy里必须得把handleClick引用存下来才能移除,匿名函数或者重新声明的函数是移除不掉的。以后遇到这种问题,记住“对比快照 + 看 Retainers 引用链”这招,基本上一抓一个准。