Vue 内存排查可以先问一个具体问题:组件创建的外部资源,谁负责结束?DOM 被移除,不代表 window 监听、定时器、第三方实例或未完成请求都已失去引用。
垃圾回收看的是可达性
对象是否还能从运行环境的根引用到,比“离开了哪个函数”更关键。闭包、数组和缓存本身不是错误;只有当不再需要的数据持续被引用,才需要追踪保留路径。
把一个局部变量赋成 null,也不能删除其他对象对它的引用。WeakMap 可用于某些关联数据,但不能自动取消事件、关闭连接或停止定时器。
用创建与清理配对
下面是 Vue 3 中监听窗口变化和记录计时次数的示例:
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
const width = ref(0);
const ticks = ref(0);
let timer = null;
function updateWidth() {
width.value = window.innerWidth;
}
onMounted(() => {
updateWidth();
window.addEventListener("resize", updateWidth);
timer = window.setInterval(() => {
ticks.value += 1;
}, 1000);
});
onUnmounted(() => {
window.removeEventListener("resize", updateWidth);
window.clearInterval(timer);
timer = null;
});
</script>
<template>
<p>窗口宽度:{{ width }};计时:{{ ticks }}</p>
</template>
解绑时必须使用与绑定时相同的函数引用,并匹配相关监听选项。再次写一个外观相同的匿名函数,不能移除原监听。
Vue 2 对应清理常放在 beforeDestroy;官方示例也提醒第三方库创建的附加结构需要手动销毁。Vue 2 内存泄漏示例
不同资源有不同结束方式
| 资源 | 需要明确的清理动作 |
|---|---|
| timeout、interval | clearTimeout、clearInterval |
| 全局事件、事件总线 | 用原处理器解绑 |
| 图表、编辑器、地图实例 | 调用库提供的 dispose 或 destroy |
| 请求 | 按协议取消,并防止过期结果写入 |
| observer、订阅、连接 | disconnect、unsubscribe 或 close |
不要为了清理一个组件调用事件总线的“移除全部监听”,否则可能同时移除其他组件的订阅。防抖函数也应暴露 cancel,见闭包与防抖。
KeepAlive 不是卸载
被缓存的组件可能只是停用,实例仍然存在;路由只改变参数时也可能复用组件。此时需要根据业务在停用时暂停资源、激活时恢复,或监听参数变化,不能把 onUnmounted 当作每次离开视图都会执行的事件。
如何判断是否真的泄漏
按固定动作反复进入、离开页面,观察回收后的保留对象与引用路径。一次峰值上涨可能只是正常分配;持续增长、无法回收且能找到多余引用,才支持具体的泄漏判断。
堆快照还可能包含业务数据,应在受控环境分析。性能结论需要可重复的操作与证据,不能仅凭“内存看起来高”就重写整套组件。