内存泄漏源于页面销毁后未清理监听器,导致组件实例无法被垃圾回收,引发性能下降、OOM崩溃及运行时错误;Vue 2需在destroyed中$off,Vue 3须在onBeforeUnmount中显式清理;根本解法是避免全局EventBus,改用props/emits、provide/inject或模块化mitt实例。

页面销毁后监听器未清理,会导致回调函数持续持有组件实例的引用,使整个组件无法被垃圾回收机制释放——这就是典型的内存泄漏。
监听器残留直接拖慢应用性能
每个未解绑的 $on 都会维持一条从 EventBus 到组件方法的强引用链。即使组件已卸载,它的 data、methods、computed 甚至子组件树仍驻留在内存中。用户反复进出同一类页面(比如列表页→详情页→返回),泄漏会逐次叠加,最终表现为:
- 页面响应变卡顿,尤其是滚动或动画时掉帧明显
- Chrome DevTools 的 Memory 面板显示 JS Heap 持续上涨,且“Retained Size”异常偏高
- 低端设备上可能触发 OOM(内存溢出)崩溃
隐性副作用比性能更危险
泄漏不只是吃内存。残留监听器仍能接收后续 emit,而此时组件已不存在,常见后果包括:
- 调用已销毁组件的方法,抛出
Cannot read property 'xxx' of null等运行时错误 - 意外触发副作用:比如监听
'user-logout'的组件已被销毁,但 logout 事件仍执行了本该清理的 localStorage,导致下次登录状态错乱 - 多个历史实例同时响应同一事件,造成数据重复提交、UI 闪动或状态覆盖
Vue 2 和 Vue 3 的处理差异
Vue 2 中靠 beforeDestroy 或 destroyed 钩子手动 $off;Vue 3 已移除这些钩子,必须在 onBeforeUnmount 中显式清理:
- 若用的是基于 Vue 实例的传统总线(
new Vue()),漏掉$off就等于放任泄漏 - 若用 mitt 等第三方库,也需同样在卸载前调用
off(),它不自动绑定生命周期 - 没有自动清理机制——EventBus 本身不知道谁注册了、谁该下线
真正安全的做法不是“记得关”,而是“不用手动关”
靠人肉记忆清理极易遗漏。更可靠的路径是规避全局总线本身:
- 优先用
props/emits或defineEmits处理父子通信 - 兄弟组件用
provide/inject+ reactive 对象共享状态 - 跨模块通知改用类型安全的模块化 mitt 实例(如
userEventBus),配合组合式 API 的自动清理逻辑

















