Vue 将事件系统拆分为原生 DOM 事件与自定义组件事件两套正交实现,前者对接浏览器事件循环,后者基于发布--订阅模型。本文以 v-on
(缩写 @
)为线索,结合运行时源码路径,给出端到端的实现剖析。
一、架构概览
Vue 的事件绑定分为两条主线:
-
原生事件绑定
通过
@click
或v-on:click
直接作用于普通 DOM 元素,最终调用浏览器的addEventListener
。 -
组件事件绑定
通过
@click
作用于子组件标签时,实际上是父组件监听子组件的自定义事件,由子组件通过$emit
触发,不经过 DOM。
二、原生事件绑定:从 AST 到 addEventListener
1.编译阶段
模板中的 @click="handler"
经模板编译器解析后,生成 AST,最终转化为 VNode 的 data.on = { click: handler }
。
2.运行时挂载
首次渲染时,patch
过程会调用 createElm
,为真实 DOM 节点执行 invokeCreateHooks
,其中 cbs.create
包含 updateDOMListeners
(位于 src/platforms/web/runtime/modules/events.js
)。
updateDOMListeners
的职责:
- 归一化事件名,处理 IE 兼容性差异。
- 生成包裹函数,处理
.once
、.passive
、.capture
等修饰符。 - 调用
updateListeners
→add
→target.addEventListener(type, wrappedHandler, useCapture)
。
3.更新阶段
当组件更新时,patch
再次调用 updateDOMListeners
,通过 sameVnode
判断事件差异,按需移除旧事件并重新绑定新事件。
三、组件事件绑定:on + events + emit
1.父组件编译
<Child @click="handleClick" />
编译后,VNode 的 componentOptions.listeners = { click: handleClick }
,不会出现在 DOM 属性上。
2.子组件初始化
子组件实例化时:
initInternalComponent
将父级listeners
注入到vm.$options._parentListeners
。initEvents
创建_events = Object.create(null)
作为事件中心。- 若
_parentListeners
非空,执行updateComponentListeners(vm, _parentListeners)
,内部通过$on
注册事件:
js
function add(event, fn) {
vm.$on(event, fn)
}
3.手动触发
子组件内部调用 this.$emit('click', payload)
时,执行:
js
const cbs = vm._events[event]
if (cbs) {
cbs.forEach(cb => cb(payload))
}
整个过程与浏览器事件体系完全隔离,因此可跨层级通信,且参数可控。
四、.native:在组件根节点强制使用原生事件
<Child @click.native="handler" />
编译为 nativeOn
而非 on
,运行时由 updateDOMListeners
读取 nativeOn
,流程与原生事件一致,绑定在组件根 DOM 上。
五、事件修饰符实现细节
.stop
:包裹函数内调用e.stopPropagation()
。.prevent
:包裹函数内调用e.preventDefault()
。.once
:绑定后立即移除监听器,并标记_withOnce
。.passive
:调用addEventListener(type, fn, { passive: true })
。.capture
:第三个参数传入useCapture: true
。
六、性能与内存考量
- 原生事件由浏览器托管,Vue 仅在 VNode 销毁时执行
removeEventListener
,无额外开销。 - 组件事件存储在 JS 对象,组件销毁时统一
$off
,防止内存泄漏。
结论
Vue 事件系统通过"编译期转换 + 运行时调度"实现高度抽象:
- 原生事件:AST → VNode → patch → addEventListener,完全对齐浏览器。
- 组件事件:父子间通过 VNode.listeners → vm.events → emit,脱离 DOM,实现跨组件通信。
理解这一分层设计,有助于在复杂场景(服务端渲染、微前端、自定义渲染器)中精准定位事件相关问题。