vue 的 patch 通过精准比对事件监听器引用实现优化:仅在 handler 改变时解绑重绑,拒绝内联函数导致的无效更新,并区分原生事件、组件事件和第三方事件的清理策略。

Vue 的 patch 过程本身不“优化绑定”,而是通过精准比对和引用控制,避免无效操作——这才是真正的性能优化:不重复绑、不漏解绑、不误清理。
只在必要时解绑+重绑
patch 不是每次更新都暴力移除再添加。它会先比对新旧 vnode 的 on 对象:
- 如果旧 vnode 有
@click="a",新 vnode 没有 click 事件 → 直接调用removeEventListener('click', a) - 如果新旧都有 click,但 handler 引用不同(比如从
handleA改成handleB)→ 先解绑handleA,再绑定handleB - 如果 handler 引用完全一致(同一函数)→ 什么也不做,复用已有监听器
拒绝内联函数带来的无效更新
写成 @click="() => doX()" 看似简洁,但每次 render 都生成新函数,导致 patch 总是判定为“引用不同”,从而反复解绑+重绑。
- 这不是内存泄漏,但浪费 CPU 和事件注册开销
- 正确做法:把 handler 提前定义在
methods或用useCallback类方式稳定引用 - 修饰符(如
@click.once)会被编译成带状态的 wrapper 函数,patch 仍能准确识别并清理
原生事件与自定义事件分开治理
patch 的事件逻辑只管 DOM 原生事件(@click、@input 等),其它两类需开发者主动管理:
-
自定义组件事件(如
@update:modelValue):由 Vue 实例内部事件系统管理,卸载发生在组件unmounted钩子中 -
第三方库绑定的事件(如 Mermaid.js、日期选择器):绕过 vnode 流程,必须在
beforeUnmount或unmounted中显式调用destroy()或off()
验证是否真被清理了
怀疑事件没卸载?别猜,用 Chrome DevTools 定向查:
- 打开 Memory 面板 → 录制 Allocation Timeline
- 筛选
EventListener对象,观察增长趋势 - 对比快照,看未回收的 handler 是否还被 DOM 元素或闭包持有
- 重点检查 keep-alive 组件中残留的监听器、未清除的全局
window.addEventListener











