深入理解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 相关文章推荐
实用的Jquery选项卡TAB示例代码
Aug 28 Javascript
JS保留两位小数,多位小数的示例代码
Jan 07 Javascript
jQuery+PHP实现动态数字展示特效
Mar 14 Javascript
js使用setTimeout实现定时炸弹的方法
Apr 10 Javascript
jquery表单验证插件formValidator使用方法
Apr 01 Javascript
AngularJS教程之MVC体系结构详解
Aug 16 Javascript
js显示动态时间的方法详解
Aug 20 Javascript
AngularJS操作键值对象类似java的hashmap(填坑小结)
Nov 12 Javascript
微信小程序开发之Tabbar实例详解
Jan 09 Javascript
jQuery点击导航栏选中更换样式的实现代码
Jan 23 Javascript
BootStrap 导航条实例代码
May 18 Javascript
解决Layui 表单提交数据为空的问题
Aug 15 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操作mysql函数详解,mysql和php交互函数
2011/05/19 PHP
PHP程序员的技术成长规划
2016/03/25 PHP
PHP图形计数器程序显示网站用户浏览量
2016/07/20 PHP
php-7.3.6 编译安装过程
2020/02/11 PHP
jQuery LigerUI 使用教程入门篇
2012/01/18 Javascript
Jquery操作下拉框(DropDownList)实现取值赋值
2013/08/13 Javascript
使用Node.js为其他程序编写扩展的基本方法
2015/06/23 Javascript
ionic js 复选框 与普通的 HTML 复选框到底有没区别
2016/06/06 Javascript
JS实现输入框提示文字点击时消失效果
2016/07/19 Javascript
Angular中使用ui router实现系统权限控制及开发遇到问题
2016/09/23 Javascript
Vue实现双向绑定的方法
2016/12/22 Javascript
React-Native 组件之 Modal的使用详解
2017/08/08 Javascript
bootstrap-Treeview实现级联勾选
2017/11/23 Javascript
用jquery获取select标签中选中的option值及文本的示例
2018/01/25 jQuery
解决Vue.js 2.0 有时双向绑定img src属性失败的问题
2018/03/14 Javascript
在vue组件中使用axios的方法
2018/03/16 Javascript
js实现ATM机存取款功能
2020/10/27 Javascript
vue实现一个6个输入框的验证码输入组件功能的实例代码
2020/06/29 Javascript
python logging日志模块的详解
2017/10/29 Python
pycharm远程调试openstack的图文教程
2017/11/21 Python
python安装scipy的步骤解析
2019/09/28 Python
Python魔法方法 容器部方法详解
2020/01/02 Python
浅谈Python程序的错误:变量未定义
2020/06/02 Python
python怎么对数字进行过滤
2020/07/05 Python
Python猫眼电影最近上映的电影票房信息
2020/09/18 Python
小学英语教学反思
2014/01/30 职场文书
竞选劳动委员演讲稿
2014/04/28 职场文书
项目合作意向书模板
2014/07/29 职场文书
2014年最新领导班子整改方案
2014/09/27 职场文书
授权委托书范本(单位)
2014/09/28 职场文书
2015年体育部工作总结
2015/04/02 职场文书
什么是求职信?求职信应包含哪些内容?
2019/08/14 职场文书
HTML基础-标签分类(闭合标签,空标签,块级元素,行内元素,行级块元素,可替换元素)
2021/03/31 HTML / CSS
springBoot基于webSocket实现扫码登录
2021/06/22 Java/Android
千万级用户系统SQL调优实战分享
2022/03/03 MySQL
apache虚拟主机配置的三种方式(小结)
2022/07/23 Servers