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、intervalclearTimeout、clearInterval
全局事件、事件总线用原处理器解绑
图表、编辑器、地图实例调用库提供的 dispose 或 destroy
请求按协议取消,并防止过期结果写入
observer、订阅、连接disconnect、unsubscribe 或 close

不要为了清理一个组件调用事件总线的“移除全部监听”,否则可能同时移除其他组件的订阅。防抖函数也应暴露 cancel,见闭包与防抖。

KeepAlive 不是卸载

被缓存的组件可能只是停用,实例仍然存在;路由只改变参数时也可能复用组件。此时需要根据业务在停用时暂停资源、激活时恢复,或监听参数变化,不能把 onUnmounted 当作每次离开视图都会执行的事件。

如何判断是否真的泄漏

按固定动作反复进入、离开页面,观察回收后的保留对象与引用路径。一次峰值上涨可能只是正常分配;持续增长、无法回收且能找到多余引用,才支持具体的泄漏判断。

堆快照还可能包含业务数据,应在受控环境分析。性能结论需要可重复的操作与证据,不能仅凭“内存看起来高”就重写整套组件。