Vue 事件绑定机制

Vue 将事件系统拆分为原生 DOM 事件与自定义组件事件两套正交实现,前者对接浏览器事件循环,后者基于发布--订阅模型。本文以 v-on(缩写 @)为线索,结合运行时源码路径,给出端到端的实现剖析。

一、架构概览

Vue 的事件绑定分为两条主线:

  • 原生事件绑定

    通过 @clickv-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 等修饰符。
  • 调用 updateListenersaddtarget.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,实现跨组件通信。

理解这一分层设计,有助于在复杂场景(服务端渲染、微前端、自定义渲染器)中精准定位事件相关问题。

相关推荐
泷羽Sec-静安15 分钟前
Less-7 GET-Dump into outfile-String
android·前端·网络·sql·安全·web安全
IT_陈寒30 分钟前
从2秒到200ms:我是如何用JavaScript优化页面加载速度的🚀
前端·人工智能·后端
天天向上102441 分钟前
vue 网站导航栏
前端·javascript·vue.js
superior tigre42 分钟前
(huawei)最小栈
c++·华为·面试
云外天ノ☼1 小时前
一、Node.js入门实战指南:从零搭建你的第一个后端
前端·javascript·笔记·node.js
m0_736927041 小时前
Spring Boot项目中如何实现接口幂等
java·开发语言·spring boot·后端·spring·面试·职场和发展
未来之窗软件服务1 小时前
未来之窗昭和仙君(四十八)开发商品进销存修仙版——东方仙盟筑基期
前端·仙盟创梦ide·东方仙盟·昭和仙君·东方仙盟架构
风清云淡_A2 小时前
【REACT16】react老项目版本依赖适配问题
前端·react.js
jump6802 小时前
【react】 useEffect
前端
前端小咸鱼一条2 小时前
16.React性能优化SCU
前端·react.js·性能优化