Vue的patch过程自动卸载与更新事件监听器:旧vnode有而新vnode无on时遍历解绑;新旧同名事件handler引用不同时先解绑再绑定;自定义组件事件和第三方库事件不在patch卸载范围内。

Vue 的 patch 过程会自动比对新旧 vnode 的事件配置,按需卸载旧监听、绑定新监听,不依赖开发者手动 removeEventListener。关键在于它通过引用比对和结构化清理,从机制上规避了因忘记解绑导致的内存泄漏。
事件监听器何时被卸载
当 oldVnode 有 data.on(例如 @click="handleA"),而 newVnode 的 data.on 为 null、undefined 或空对象时,patch 会进入卸载流程:
- 遍历 oldVnode 所有事件名(如
click、input) - 对每个事件调用原生
el.removeEventListener(type, handler) - 该过程不判断函数是否“逻辑相等”,只依据绑定时缓存的原始 handler 引用
事件更新时如何避免重复绑定
若新旧 vnode 都定义了同名事件(如都含 onClick),但 handler 函数引用不同,patch 会执行“先解绑、再绑定”:
分析 Vue 3 Composition API 使用情况——审计响应式模式、可组合函数设计、ref 与 reactive 的选择、计算属性、watcher 使用及组件……
- 例如从
@click="handleA"更新为@click="handleB",会移除handleA并添加handleB - 但若写成
@click="() => doX()",每次渲染都生成新函数,导致每次都触发解绑+重绑——这不是泄漏,但浪费性能且可能干扰事件行为 - 正确做法是将 handler 提前定义在
methods或用useCallback类方式稳定引用
哪些事件不在 patch 卸载范围内
patch 的事件清理仅作用于 DOM 原生事件(@click、@input 等),以下两类需额外关注:
-
自定义组件事件(如
@update:modelValue):由 Vue 实例内部事件系统管理,卸载发生在组件unmounted钩子中,与 DOM patch 无关 -
第三方库绑定的事件(如 bootstrap-datepicker、Mermaid.js):它们绕过 Vue 的 vnode 流程,必须在组件销毁时显式调用
destroy()或off()
验证与排查建议
若怀疑仍有泄漏,可结合 Chrome DevTools 的 Memory 面板做定向分析:
- 录制 Allocation Timeline,筛选
EventListener对象增长趋势 - 对比两次快照,查看未被回收的 handler 是否仍被 DOM 元素或闭包持有
- 重点检查内联函数、未清除的
addEventListener、keep-alive 缓存组件中的事件残留

















