深入理解Vue.js源码之事件机制

发布时间 - 2026-01-11 03:26:05    点击率:

写在前面

因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出。

文章的原地址:https://github.com/answershuto/learnVue。

在学习过程中,为Vue加上了中文的注释https://github.com/answershuto/learnVue/tree/master/vue-src,希望可以对其他想学习Vue源码的小伙伴有所帮助。
可能会有理解存在偏差的地方,欢迎提issue指出,共同学习,共同进步。

Vue事件API

众所周知,Vue.js为我们提供了四个事件API,分别是$on](https://cn.vuejs.org/v2/api/#vm-on-event-callback),[$once,$off](https://cn.vuejs.org/v2/api/#vm-off-event-callback),[$emit。

初始化事件

初始化事件在vm上创建一个_events对象,用来存放事件。_events的内容如下:

{
  eventName: [func1, func2, func3]
}

存放事件名以及对应执行方法。

/*初始化事件*/
export function initEvents (vm: Component) {
 /*在vm上创建一个_events对象,用来存放事件。*/
 vm._events = Object.create(null)
 /*这个bool标志位来表明是否存在钩子,而不需要通过哈希表的方法来查找是否有钩子,这样做可以减少不必要的开销,优化性能。*/
 vm._hasHookEvent = false
 // init parent attached events
 /*初始化父组件attach的事件*/
 const listeners = vm.$options._parentListeners
 if (listeners) {
  updateComponentListeners(vm, listeners)
 }
}

$on

$on方法用来在vm实例上监听一个自定义事件,该事件可用$emit触发。

 Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
  const vm: Component = this

  /*如果是数组的时候,则递归$on,为每一个成员都绑定上方法*/
  if (Array.isArray(event)) {
   for (let i = 0, l = event.length; i < l; i++) {
    this.$on(event[i], fn)
   }
  } else {
   (vm._events[event] || (vm._events[event] = [])).push(fn)
   // optimize hook:event cost by using a boolean flag marked at registration
   // instead of a hash lookup
   /*这里在注册事件的时候标记bool值也就是个标志位来表明存在钩子,而不需要通过哈希表的方法来查找是否有钩子,这样做可以减少不必要的开销,优化性能。*/
   if (hookRE.test(event)) {
    vm._hasHookEvent = true
   }
  }
  return vm
 }

$once

$once监听一个只能触发一次的事件,在触发以后会自动移除该事件。

 Vue.prototype.$once = function (event: string, fn: Function): Component {
  const vm: Component = this
  function on () {
   /*在第一次执行的时候将该事件销毁*/
   vm.$off(event, on)
   /*执行注册的方法*/
   fn.apply(vm, arguments)
  }
  on.fn = fn
  vm.$on(event, on)
  return vm
 }

$off

$off用来移除自定义事件

Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
  const vm: Component = this
  // all
  /*如果不传参数则注销所有事件*/
  if (!arguments.length) {
   vm._events = Object.create(null)
   return vm
  }
  // array of events
  /*如果event是数组则递归注销事件*/
  if (Array.isArray(event)) {
   for (let i = 0, l = event.length; i < l; i++) {
    this.$off(event[i], fn)
   }
   return vm
  }
  // specific event
  const cbs = vm._events[event]
  /*Github:https://github.com/answershuto*/
  /*本身不存在该事件则直接返回*/
  if (!cbs) {
   return vm
  }
  /*如果只传了event参数则注销该event方法下的所有方法*/
  if (arguments.length === 1) {
   vm._events[event] = null
   return vm
  }
  // specific handler
  /*遍历寻找对应方法并删除*/
  let cb
  let i = cbs.length
  while (i--) {
   cb = cbs[i]
   if (cb === fn || cb.fn === fn) {
    cbs.splice(i, 1)
    break
   }
  }
  return vm
 }

$emit

$emit用来触发指定的自定义事件。

Vue.prototype.$emit = function (event: string): Component {
  const vm: Component = this
  if (process.env.NODE_ENV !== 'production') {
   const lowerCaseEvent = event.toLowerCase()
   if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
    tip(
     `Event "${lowerCaseEvent}" is emitted in component ` +
     `${formatComponentName(vm)} but the handler is registered for "${event}". ` +
     `Note that HTML attributes are case-insensitive and you cannot use ` +
     `v-on to listen to camelCase events when using in-DOM templates. ` +
     `You should probably use "${hyphenate(event)}" instead of "${event}".`
    )
   }
  }
  let cbs = vm._events[event]
  if (cbs) {
   /*将类数组的对象转换成数组*/
   cbs = cbs.length > 1 ? toArray(cbs) : cbs
   const args = toArray(arguments, 1)
   /*遍历执行*/
   for (let i = 0, l = cbs.length; i < l; i++) {
    cbs[i].apply(vm, args)
   }
  }
  return vm
 }

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


# Vue.js  # 事件机制  # 浅谈Vue2.0父子组件间事件派发机制  # 一篇文章带你了解vue.js的事件循环机制  # vue @click与@click.native  # 及vue事件机制的使用分析  # 递归  # 自定义  # 遍历  # 而不  # 这样做  # 方法来  # 创建一个  # 移除  # 可以减少  # 是个  # 会有  # 也就  # 上了  # 感兴趣  # 对其  # 不存在  # 花了  # 他想  # 这几个  # 转换成 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Laravel storage目录权限问题_Laravel文件写入权限设置  javascript中的try catch异常捕获机制用法分析  Gemini怎么用新功能实时问答_Gemini实时问答使用【步骤】  Laravel如何安装使用Debugbar工具栏_Laravel性能调试与SQL监控插件【步骤】  Windows家庭版如何开启组策略(gpedit.msc)?(安装方法)  Linux系统运维自动化项目教程_Ansible批量管理实战  如何用低价快速搭建高质量网站?  JavaScript中的标签模板是什么_它如何扩展字符串功能  Laravel如何实现数据导出到PDF_Laravel使用snappy生成网页快照PDF【方案】  详解CentOS6.5 安装 MySQL5.1.71的方法  Laravel怎么生成URL_Laravel路由命名与URL生成函数详解  详解ASP.NET 生成二维码实例(采用ThoughtWorks.QRCode和QrCode.Net两种方式)  Laravel Pest测试框架怎么用_从PHPUnit转向Pest的Laravel测试教程  简历在线制作网站免费版,如何创建个人简历?  javascript读取文本节点方法小结  UC浏览器如何设置启动页 UC浏览器启动页设置方法  HTML5空格在Angular项目里怎么处理_Angular中空格的渲染问题【详解】  Laravel怎么实现软删除SoftDeletes_Laravel模型回收站功能与数据恢复【步骤】  Midjourney怎样加参数调细节_Midjourney参数调整技巧【指南】  如何在IIS中新建站点并配置端口与IP地址?  标准网站视频模板制作软件,现在有哪个网站的视频编辑素材最齐全的,背景音乐、音效等?  Win11应用商店下载慢怎么办 Win11更改DNS提速下载【修复】  如何实现javascript表单验证_正则表达式有哪些实用技巧  网站优化排名时,需要考虑哪些问题呢?  Claude怎样写结构化提示词_Claude结构化提示词写法【教程】  手机软键盘弹出时影响布局的解决方法  今日头条微视频如何找选题 今日头条微视频找选题技巧【指南】  免费网站制作appp,免费制作app哪个平台好?  详解Android图表 MPAndroidChart折线图  Laravel怎么创建自己的包(Package)_Laravel扩展包开发入门到发布  HTML5空格和margin有啥区别_空格与外边距的使用场景【说明】  html5如何实现懒加载图片_ intersectionobserver api用法【教程】  详解免费开源的.NET多类型文件解压缩组件SharpZipLib(.NET组件介绍之七)  JavaScript 输出显示内容(document.write、alert、innerHTML、console.log)  uc浏览器二维码扫描入口_uc浏览器扫码功能使用地址  昵图网官方站入口 昵图网素材图库官网入口  厦门模型网站设计制作公司,厦门航空飞机模型掉色怎么办?  Microsoft Edge如何解决网页加载问题 Edge浏览器加载问题修复  深圳网站制作设计招聘,关于服装设计的流行趋势,哪里的资料比较全面?  HTML5打空格有哪些误区_新手常犯的空格使用错误【技巧】  如何在IIS7上新建站点并设置安全权限?  Laravel如何记录自定义日志?(Log频道配置)  高配服务器限时抢购:企业级配置与回收服务一站式优惠方案  微信公众帐号开发教程之图文消息全攻略  三星网站视频制作教程下载,三星w23网页如何全屏?  Laravel API路由如何设计_Laravel构建RESTful API的路由最佳实践  Laravel如何记录日志_Laravel Logging系统配置与自定义日志通道  Windows驱动无法加载错误解决方法_驱动签名验证失败处理步骤  百度输入法ai面板怎么关 百度输入法ai面板隐藏技巧  简单实现Android文件上传