在开发中后台或者数据可视化页面时,我们经常会遇到 内存泄露 的问题,尤其是使用 Vue 和 ECharts 时。本文将结合实际项目经验,详细讲解如何排查引用导致的内存泄露,并提供实用的优化方案。
一、现象分析
在我们的项目中,有一个页面需要循环展示一百个echarts图表,每次点击刷新或者翻页后:
- 页面依旧能够正常渲染图表;
- 但是使用 Chrome DevTools 的 Heap Snapshot 工具发现:
-
- 堆内存持续增长;
-
- Snapshot 体积越来越大。
这说明页面存在 内存泄露
1️⃣ 准备工作:捕获快照
-
打开 Chrome DevTools → Memory。
-
点击可疑的操作,比如下图是我重复点击四次刷新后的效果
点击新一轮刷新后上一轮的线条居高不下,说明内存泄露
3. 清空快照并刷新页面,按以下操作获取初始状态的快照
4. 重复可疑操作(我这里是点击刷新按钮更新图表数据),生成第二个快照5. 最终你会有两个快照:前和后
2️⃣ 对比快照:找新增对象
-
选择 第二个快照。
-
下拉选择右侧的 Comparison(比较) 面板:
- 选择要对比的快照,默认会与上一个对比
- 这里会显示所有新增对象的 数量 和 类型。
-
输入datached过滤(datached是分离的意思,也就是脱离了文档流的元素,但是没有被释放)
-
我们看No. delta,+表示新增的元素,然后随便选择一个再观察Retainers(Retainer是保留的意思),我们可以看到发生在echarts库,于是大胆猜测刷新过后echart实例没释放。
-
然后在回到自己的代码刷新按钮点击后的逻辑处加上销毁echart实例的逻辑
- 然后我们再按上面的方法对比快照,发现Detached已经没了
二、分析内存泄露原因
1. ECharts 实例累积
renderChart 函数每次都会:
var myChart = echarts.init(document.getElementById(chart.id));
myChart.setOption(option);
- 没有销毁旧实例,导致每次渲染产生新的 ECharts 对象;
- DOM 节点未解绑旧实例 → Detached DOM。
2. 事件监听器闭包
- 事件闭包会引用
chart数据; - 累积点击按钮 → 旧数据依然存在 → 内存不断增长。
- 可能触发旧 DOM 替换 → Detached DOM → 脱离文档流却存在内存中。
三、解决方案
1. 在渲染前销毁旧 ECharts 实例
renderChart(chart) {
// 如果已有实例,先销毁
if (chart._echartsInstance) {
chart._echartsInstance.dispose();
}
const el = document.getElementById(chart.id);
const myChart = echarts.init(el);
chart._echartsInstance = myChart;
myChart.setOption(option);
}
核心:每个 chart 只保留一个实例,旧实例先
dispose()。
2. 手动清理图表实例
this.chartsData.forEach(chart => {
if (chart._echartsInstance) chart._echartsInstance.dispose();
});
this.chartsData = [];
- 在更新
chartsData前清理旧实例,释放内存。
四、排查技巧总结
- 观察 Heap Snapshot 的体积变化
-
- 一次增长并不意味着一定是内存泄露,有可能是刷新后做了正常的数据缓存,多刷新几次,每次都打快照,如果持续增长,说明内存可能泄露;
- 沿 Retainers 找真正的业务引用。
- 对比操作前后快照
-
- 找新增对象和引用链;
- 可以判断是 DOM、闭包、事件还是第三方库缓存导致。
- 关注 ECharts、事件和 DOM 的生命周期
-
- 每次重新渲染前销毁实例;
- 避免重复注册事件;
- Vue 组件卸载时清理实例。
五、效果对比
| 优化前 | 优化后 |
|---|---|
| 点击按钮一次,快照增加几十 MB | 内存稳定增长几乎消失 |
| Detached DOM 数量累积 | Detached DOM 减少,system持有对象不再增长 |
| Heap Snapshot 体积快速膨胀 | Heap Snapshot 体积稳定 |
六、结语
内存泄露不仅影响性能,还可能导致应用崩溃。
在 Vue + ECharts 的项目中,实例管理、事件解绑和 DOM 生命周期清理是关键点。