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


Posted in Javascript onSeptember 27, 2017

写在前面

因为对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
 }

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

Javascript 相关文章推荐
Prototype源码浅析 String部分(一)之有关indexOf优化
Jan 15 Javascript
深入理解JavaScript编程中的同步与异步机制
Jun 24 Javascript
JavaScript中的操作符类型转换示例总结
May 30 Javascript
jQuery增加、删除及修改select option的方法
Aug 19 Javascript
JS双击变input框批量修改内容
Dec 12 Javascript
jQuery实现圣诞节礼物动画案例解析
Dec 25 Javascript
移动端点击态处理的三种实现方式
Jan 12 Javascript
简单实现js选项卡切换效果
Feb 09 Javascript
老生常谈js中的MVC
Jul 25 Javascript
详解Nuxt.js部署及踩过的坑
Aug 07 Javascript
jQuery pjax 应用简单示例
Sep 20 jQuery
详解Typescript 内置的模块导入兼容方式
May 31 Javascript
js截取字符串功能的实现方法
Sep 27 #Javascript
详解node+express+ejs+bootstrap构建项目
Sep 27 #Javascript
Three.js基础学习之场景对象
Sep 27 #Javascript
vue父组件中获取子组件中的数据(实例讲解)
Sep 27 #Javascript
Web开发使用Angular实现用户密码强度判别的方法
Sep 27 #Javascript
基于复选框demo(分享)
Sep 27 #Javascript
EasyUI框架 使用Ajax提交注册信息的实现代码
Sep 27 #Javascript
You might like
PHP生成静态页面详解
2006/12/05 PHP
PHP 开源框架22个简单简介
2009/08/24 PHP
关于Sphinx创建全文检索的索引介绍
2013/06/25 PHP
PHP+Ajax检测用户名或邮件注册时是否已经存在实例教程
2014/08/23 PHP
PHP后台微信支付和支付宝支付开发
2017/04/28 PHP
SCP远程VPS快速搬家和WDCP升级php5.3安装memcached和eaccelerator教程
2017/07/27 PHP
在js中判断checkboxlist(.net控件客户端id)是否有选中
2013/04/11 Javascript
JavaScript实现的日期控件具体代码
2013/11/18 Javascript
jquery数组过滤筛选方法grep()简介
2014/06/06 Javascript
javascript如何实现暂停功能
2015/11/06 Javascript
深入浅析JavaScript中的3DES
2016/08/24 Javascript
Bootstrap3 Grid system原理及应用详解
2016/09/30 Javascript
JS实现按钮添加背景音乐示例代码
2017/10/17 Javascript
详解vue静态资源打包中的坑与解决方案
2018/02/05 Javascript
js拖动滑块和点击水波纹效果实例代码
2018/10/16 Javascript
jQuery pagination分页示例详解
2018/10/23 jQuery
NodeJs生成sitemap站点地图的方法示例
2019/06/11 NodeJs
React传值 组件传值 之间的关系详解
2019/08/26 Javascript
JS基础之逻辑结构与循环操作示例
2020/01/19 Javascript
js数组相减简单示例【删除a数组所有与b数组相同元素】
2020/03/04 Javascript
Vue 列表页带参数进详情页的操作(router-link)
2020/11/13 Javascript
原生js实现移动小球(碰撞检测)
2020/12/17 Javascript
vue 导航守卫和axios拦截器有哪些区别
2020/12/19 Vue.js
[05:46]DOTA2英雄梦之声_第18期_陈
2014/06/20 DOTA
python实现简单中文词频统计示例
2017/11/08 Python
python web基础之加载静态文件实例
2018/03/20 Python
如何基于线程池提升request模块效率
2020/04/18 Python
Pytorch转tflite方式
2020/05/25 Python
学python爬虫能做什么
2020/07/29 Python
python进行二次方程式计算的实例讲解
2020/12/06 Python
Ticketmaster德国票务网站:购买音乐会和体育等门票
2016/11/14 全球购物
培训主管的职业生涯规划
2014/03/06 职场文书
寒山寺导游词
2015/02/03 职场文书
简历自我评价优缺点
2015/03/11 职场文书
红高粱观后感
2015/06/10 职场文书
九年级英语教学反思
2016/02/15 职场文书