前言
前端应用的内存泄露指不再使用的内存未被释放,导致页面占用内存持续增长。轻则引发页面卡顿、加载缓慢,重则导致浏览器崩溃。尤其在单页应用(SPA)中,路由切换频繁但内存不回收,问题会被无限放大。例如用户长时间使用某后台管理系统,可能出现操作响应延迟,甚至需要强制刷新才能恢复,这很可能是内存泄露在'作祟'。
一、前端常见的内存泄露场景
- 意外的全局变量:未声明的变量(如
a = 10而非let a = 10)会挂载到 window 上,页面不刷新就不会释放。 - 闭包滥用:闭包会保留对外部作用域的引用,若长期持有 DOM 或大型对象,会导致内存无法回收(如未清理的事件监听回调)。
- 未清理的 DOM 引用:删除 DOM 节点后,仍保留其引用(如
let el = document.getElementById('test'),删除 el 对应的 DOM 后未置el = null)。 - 定时器 / 事件监听未销毁:
setInterval未用clearInterval清除、addEventListener未用removeEventListener解绑,尤其在组件挂载 / 卸载时容易遗漏。 - 第三方库 / 插件残留:部分第三方库(如图表库、播放器)使用后未调用销毁方法,导致内部资源无法释放。
- 数组 / 对象无限增长:全局缓存对象未设置过期机制,或数组持续 push 数据但未清理无效项。
二、核心工具:浏览器开发者工具 (Chrome DevTools)
1. 第一步:复现内存泄露场景
首先需要稳定复现泄露行为,比如:
- 路由切换(SPA 中反复切换 A→B→A→B);
- 点击按钮触发某操作(如打开弹窗后关闭);
- 长时间滚动或轮询请求数据。
2. 第二步:开启 Memory 面板
- 打开 Chrome 开发者工具。
- 勾选面板顶部的「Memory」选项(同时可保留默认的「CPU」「Network」)。
- 点击左上角的「录制」按钮(圆形红点),然后执行复现步骤(如切换路由 5 次)。
- 执行完成后点击「停止」,等待面板生成报告。
关键观察点:
- 报告中 [Memory] 曲线若持续上升且不回落(即使操作停止后,内存仍未下降),则大概率存在泄露。
- 若曲线在操作后能回落至初始水平,说明内存已正常回收,无泄露。
3. 第三步:定位泄露对象
用 Memory 面板定位泄露对象,主要用于记录页面堆内存的具体情况以及 JS 堆内存随加载时间线动态的分配情况。Performance 面板确认泄露后,用 Memory 面板进一步定位'哪些对象未被回收'。
常用快照类型: Memory 面板支持 3 种快照类型,按需选择:
- Heap snapshot(堆快照):捕获当前内存中所有对象的快照,可对比不同快照的差异,适合定位未回收的对象。
- Allocation instrumentation on timeline(时间线分配记录):实时记录内存分配过程,适合观察某操作期间的内存分配细节。
- Allocation sampling(分配采样):低开销的内存采样,适合快速排查大面积泄露,无需精确到单个对象。
堆快照对比操作:
- 打开 DevTools → 切换到「Memory」面板。
- 选择「Heap snapshot」,点击「Take snapshot」拍摄初始快照(记为快照 1,此时未执行任何操作)。
- 执行一次泄露复现步骤(如切换一次路由)。
- 再次点击「Take snapshot」拍摄快照 2。
- 重复复现步骤 N 次(如再切换 3 次路由),拍摄快照 3。
- 在 Memory 面板左侧的快照列表中,点击快照 2/3 的下拉框,选择「Comparison」(对比),并选择'与快照 1 对比'。
通过对比可以看出变占用的内存大小,是那些操作造成了内存泄露。堆快照就像照相机一样,能记录你当前页面的堆内存情况,每快照一次就会产生一条快照记录。刚开始执行了一次快照,记录了当时堆内存空间占用为 59.4MB,然后点击了页面中某些按钮,又执行一次快照,记录了当时堆内存空间占用为 59.7MB。并且点击对应的快照记录,能看到当时所有内存中的变量情况(结构、占总占用内存的百分比等)。

