Chrome内存快照里怎么判断是不是内存泄漏?

W″可慧 阅读 38

我在用 Chrome DevTools 的 Memory 面板拍快照,但不太会看那些 retained size 和 distance,到底怎么看才能确定是不是真的内存泄漏了?

我试过反复打开关闭一个组件,按理说应该被回收,但快照里还是能看到很多 detached DOM tree,像这样:

function createModal() {
  const div = document.createElement('div');
  div.innerHTML = '<button>Close</button>';
  document.body.appendChild(div);
  div.querySelector('button').onclick = () => {
    document.body.removeChild(div);
  };
}

这种写法是不是会导致闭包引用没释放?该怎么分析?

我来解答 赞 9 收藏
二维码
手机扫码查看
2 条解答
Top丶杏花
要判断是不是内存泄漏,关键是要看那些对象是不是还被引用着,即使它们对应的 DOM 元素已经从页面上移除了。在 Chrome DevTools 的 Memory 面板里,你可以按照这些步骤来分析:

1. 首先,先创建一些对象,然后清理掉它们。比如你提到的 createModal 函数,创建了一个模态框然后移除它。
2. 拍下第一个快照,记录下当前内存状态。
3. 再次创建并清理对象,确保它们被正确移除。
4. 再拍下一个快照。
5. 对比这两个快照,看看是否有对象的数量没有减少。特别是那些 detached DOM trees,它们应该是被垃圾回收机制清理掉的。

对于你提到的代码,主要问题是闭包可能导致的引用。在这个例子中,事件处理函数会捕获外部的 div 变量,这意味着即使 div 被移除,这个闭包依然会保持对它的引用。解决这个问题的一个方法是手动断开引用,比如在移除元素后将事件处理函数置为 null:

function createModal() {
const div = document.createElement('div');
div.innerHTML = '';
document.body.appendChild(div);
const button = div.querySelector('button');
function handleClick() {
document.body.removeChild(div);
button.onclick = null; // 断开引用
}
button.onclick = handleClick;
}


这样做可以确保在移除 DOM 元素后,相关的事件处理程序也会被清理掉,减少内存泄漏的风险。记得多拍几次快照,对比对象数量的变化,这样才能更好地判断是否有内存泄漏。
点赞
2026-03-23 13:13
程序员志鸣
判断内存泄漏就看一个指标:多拍几次快照,对比同一批对象的数量和size。如果只涨不跌,那就是泄漏了。

retained size 是关键,这个值表示这个对象以及它引用的所有东西总共占多少内存。distance 是到GC根的距离,距离越小越可疑,说明它很可能是泄漏链的源头。

你说的 detached DOM tree 就是典型问题。你那段代码确实有闭包泄漏:

function createModal() {
const div = document.createElement('div');
div.innerHTML = '<button>Close</button>';
document.body.appendChild(div);
div.querySelector('button').onclick = () => {
// 这个闭包引用了 div
document.body.removeChild(div);
};
}


点击关闭按钮后,div 虽然从 DOM 移除了,但 onclick 回调函数形成了一个闭包,这个闭包还持有着 div 的引用。事件处理器没清掉,对象就回收不掉。

怎么分析:

1. 在 Memory 面板拍下初始快照
2. 重复操作你的功能(比如打开关闭弹窗)十几次
3. 再拍一次快照
4. 选择第二个快照,切换到 Comparison 视图
5. 看 # Delta 列,正数的就是增长的对象

找到泄漏对象后,点进去看 Retainers 面板,一层层往上追,看是什么引用链保持着它。

修复办法:

function createModal() {
const div = document.createElement('div');
div.innerHTML = '<button>Close</button>';
document.body.appendChild(div);

const button = div.querySelector('button');
button.onclick = () => {
document.body.removeChild(div);
button.onclick = null; // 断开闭包引用
div.innerHTML = ''; // 清理内部元素引用
};
}


或者更彻底的做法是用 EventListener,并且确保在组件销毁时 removeEventListener。关键就是手动断开引用,让 GC 能回收。
点赞
2026-03-11 09:07