学会用 Chrome DevTools 排查内存泄漏,是前端进阶到架构层面的分水岭。这不仅能解决页面越用越卡的问题,更是大厂资深前端面试的必考实战题。
搞前端内存排查,核心舞台在 Chrome 的 Memory(内存)面板。这里有两把绝世武器:Heap Snapshot(堆快照) 和 Allocation Instrumentation on Timeline(分配时间轴) 。
下面我们直接用真实的场景,手把手带你去网页上“抓鬼”。
第一步:准备一个“蓄意泄漏”的卡顿现场
为了能百分之百抓到内存泄漏,我们在本地代码中(或者脑海中)先模拟一个最经典的“僵尸 DOM 泄漏”场景:
JavaScript
// 每次点击按钮,都往全局数组里塞一个巨大的 DOM 节点
window.zombieGarbage = [];
function createLeak() {
const bigNode = document.createElement('div');
bigNode.innerHTML = new Array(100000).join('👻 我是赖在内存不走的僵尸');
window.zombieGarbage.push(bigNode); // 被全局变量引用,垃圾回收不敢碰它
}
document.getElementById('leakBtn').addEventListener('click', createLeak);
现在,我们打开包含这段代码的网页,按下 F12,切换到 Memory 面板。
第二步:武器一 —— Heap Snapshot(堆快照对比法)
堆快照就像是给内存拍 X 光片。单看一张照片你很难看出问题,但如果拍两张照片进行“前后对比” ,什么东西在异常变大一目了然。
1. 拍照大招流程
- 进入 Memory 面板,选择第一项 Heap snapshot。
- 点击底部的 Take snapshot 按钮。此时生成 Snapshot 1(这是页面刚打开时的干净状态)。
- 回到网页上,疯狂点击那个会造成卡顿的按钮 10 次、20 次(或者反复切换几个页面标签)。
- 回到 Memory 面板,点击左上角的“小圆点”图标,再次拍一张照片,生成 Snapshot 2。
2. 肉眼擒凶:三步看懂对比视图
现在你左侧有两张照片了。点击 Snapshot 2,在面板顶部的下拉菜单中,把默认的 Summary(摘要)切换为 Comparison(对比) 。
此时,Chrome 会把这两张照片中间新增加的内存物体展示出来。我们在表格里看这三个黄金指标:
- Delta(数量增量) :如果某个东西的 Delta 表现为
+10或+20,说明它一直在增加。 - Shallow Size(自身大小) :这个对象自己占用了多少内存。
- Retained Size(保留大小) :最核心的指标! 代表这个对象以及它引用的所有子对象加起来的总大小。如果它死掉,能释放多少内存。
🔍 寻找“脱离文档的 DOM(Detached DOM)”
在过滤输入框(Class filter)里输入:Detached(或者输入 HTMLDivElement)。
你会惊奇地发现,列表里蹦出了很多黄色的整行,名字叫 Detached HTMLDivElement。
- 大白话翻译:
Detached意味着这个 DOM 已经在页面上被删除了(或者根本没挂载到页面上),但它在 JS 内存里依然活着。这就是典型的僵尸 DOM。
3. 查看 Retainers(到底是谁在拽着它不放?)
点击列表中那个巨大的 Detached HTMLDivElement,看面板最下方的 Retainers(持有者/引用链) 窗口:
- 你会看到一层层往上指的箭头,像剥洋葱一样。
- 最终,你会看到一个红色的字样:
zombieGarbage in window。 - 破案了! 浏览器明确告诉你:正是因为
window全局变量下的zombieGarbage数组死死拽着这个 DOM,所以垃圾回收车不敢动它。
第三步:武器二 —— Allocation Timeline(时间轴动态抓取)
如果堆快照对比让你觉得繁琐,那这个武器就是“实时监控录像”,它能在你操作页面的同时,用蓝色的柱子实时告诉你内存暴涨的瞬间。
1. 录制流程
- 在 Memory 面板选择第三项 Allocation instrumentation on timeline。
- 点击 Start 开始录制。此时顶部会出现一条带时间轴的横线。
- 去页面上做卡顿操作(比如点按钮、滚动列表)。
2. 看图说话:致命的“蓝色小柱子”
在录制过程中,你会看到随着你的点击,时间轴上突突突地冒出很多蓝色的垂直柱子:
-
柱子的高度:代表那一瞬间分配的内存体积。
-
柱子的颜色:
- 如果是灰色:说明那一瞬间分配了内存,但随后被垃圾回收车成功清理掉了(安全)。
- 如果是蓝色:说明分配了内存,且一直赖在内存里不走。
-
收网:点击停止。用鼠标在时间轴上框选那一排明晃晃的蓝色柱子。下面列表会自动过滤出那一瞬间导致内存暴涨的具体函数名和对象,直接点击就能跳到 Sources 面板对应的行。
🛠️ 实战避坑与高级技巧
去排查生产环境(线上混淆打包后的项目)时,Heap Snapshot 里的变量名全变成了 a、b、c 或 _t,根本看不出是 zombieGarbage。这时候怎么排查?
技巧 A:利用构造函数名(Constructor)
线上虽然代码混淆了,但是自定义的类名(Class Name)或者第三方库的构造函数名通常是保留的。
- 如果你怀疑是某个弹窗泄漏了,在过滤框里搜你封装的组件类名(如
Dialog、Editor)。 - 如果搜出来一堆已经关闭的弹窗实例,那引用链里顺藤摸瓜总能找到对应的事件监听器(EventListener)。
技巧 B:强制触发垃圾回收(Collect Garbage)
有时候你拿不准某个蓝柱子是正常的临时缓存,还是真正的泄漏。
在 Memory 面板的左上角,有一个“垃圾桶”形状的图标。在拍照或观察时间轴之前,手动点它 3-4 次。这是强行命令浏览器把所有能扫的垃圾全部扫掉。如果点完垃圾桶后,那根蓝柱子依然坚挺存在,那不用怀疑,百分之百是内存泄漏了。