Chrome DevTools 内存快照里怎么定位闭包导致的内存泄漏?

Newb.梓晨 阅读 3

我在做一个单页应用,发现切换页面后内存一直涨,用 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 不放?

我来解答 赞 2 收藏
二维码
手机扫码查看
1 条解答
Mc.秋香
Mc.秋香 Lv1
这问题太典型了,闭包加事件监听简直就是内存泄漏的黄金搭档。DevTools 其实给了很明确的路径,关键在于你要用“对比”模式,而不是盯着一个快照发呆。

操作步骤是这样的:

先进入你的页面,让组件初始化,在 Memory 面板拍个 Heap Snapshot(快照A)。然后退出页面,触发一下 GC(DevTools 里那个带扫把的圆圈图标),再拍第二个快照(快照B)。

重点来了,选中快照B,把左侧视图从 Summary 切换成 Comparison,对比基准选快照A。这时候看 Delta 列,找那些正数且内存占用大的对象,比如你那个 data 数组,肯定排在前面。

点进去这个对象,看下方的 Retainers 面板,这里就是案发现场。你会看到一条引用链,通常长这样:某个 DOM 节点 -> 事件监听器 -> 闭包上下文 -> 你的 data 变量。如果 DOM 节点是 Detached DOM tree,那就说明 DOM 没了,但事件监听器还活着,它死死抓着闭包不放。

至于代码修复,你那个 destroy 里必须得把 handleClick 引用存下来才能移除,匿名函数或者重新声明的函数是移除不掉的。

function createWidget(container) {
let data = fetchHugeData(); // 假设这玩意儿很大

// 必须把函数存到变量里,否则 removeEventListener 无效
const handleClick = () => {
console.log(data.length);
};

container.addEventListener('click', handleClick);

return {
destroy() {
// 核心修复:移除监听
container.removeEventListener('click', handleClick);
// 可选:手动切断引用,虽然移除监听后闭包会被回收,但置空是个好习惯
data = null;
}
};
}


以后遇到这种问题,记住“对比快照 + 看 Retainers 引用链”这招,基本上一抓一个准。
点赞
2026-03-04 12:14