深入理解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 相关文章推荐
不懂JavaScript应该怎样学
Apr 16 Javascript
非常不错的功能强大代码简单的管理菜单美化版
Jul 09 Javascript
Javascript在IE或Firefox下获取鼠标位置的代码
Dec 18 Javascript
js实现数组去重、判断数组以及对象中的内容是否相同
Nov 29 Javascript
javascript查询字符串参数的方法
Jan 28 Javascript
JavaScript创建闭包的两种方式的优劣与区别分析
Jun 22 Javascript
jQuery动态添加及删除表单上传元素的方法(附demo源码下载)
Jan 15 Javascript
Bootstrap学习系列之使用 Bootstrap Typeahead 组件实现百度下拉效果
Jul 07 Javascript
js删除Array数组中指定元素的两种方法
Aug 03 Javascript
Vue自定义指令拖拽功能示例
Feb 17 Javascript
less简单入门(CSS 预处理语言)
Mar 08 Javascript
javaScript之split与join的区别(详解)
Nov 08 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 XMLWriter类的简单示例代码(RSS输出)
2011/09/30 PHP
php解压文件代码实现php在线解压
2014/02/13 PHP
php使用COPY函数更新配置文件的方法
2015/06/18 PHP
PHP读取XML格式文件的方法总结
2017/02/27 PHP
JavaScript中的Document文档对象
2008/01/16 Javascript
IE与firefox下Dhtml的一些区别小结
2009/12/02 Javascript
JavaScript学习心得之概述
2015/01/20 Javascript
浅谈JavaScript中的Math.atan()方法的使用
2015/06/14 Javascript
D3.js实现饼状图的方法详解
2016/09/21 Javascript
vue-loader教程介绍
2017/06/14 Javascript
vue-router单页面路由
2017/06/17 Javascript
JS对日期操作封装代码实例
2019/11/08 Javascript
微信小程序动态设置图片大小的方法
2019/11/21 Javascript
js实现上传图片并显示图片名称
2019/12/18 Javascript
python+django加载静态网页模板解析
2017/12/12 Python
详解python配置虚拟环境
2019/04/08 Python
Python实现的读取文件内容并写入其他文件操作示例
2019/04/09 Python
python+pygame实现坦克大战
2019/09/10 Python
vue学习笔记之动态组件和v-once指令简单示例
2020/02/29 Python
详解如何修改jupyter notebook的默认目录和默认浏览器
2021/01/24 Python
HTML5 客户端数据库简易使用:IndexedDB
2019/12/19 HTML / CSS
意大利专业化妆品品牌:KIKO MILANO
2017/02/01 全球购物
6PM官网:折扣鞋、服装及配饰
2018/08/03 全球购物
Myprotein瑞士官方网站:运动营养和健身网上商店
2019/09/25 全球购物
Jowissa官方网站:瑞士制造的手表,优雅简约的设计
2020/07/29 全球购物
护理职业应聘自荐书
2013/09/29 职场文书
2014保险公司内勤工作总结
2014/12/16 职场文书
小学优秀学生评语
2014/12/29 职场文书
2015年人事科工作总结
2015/04/28 职场文书
生日祝酒词大全
2015/08/10 职场文书
大学生奖学金获奖感言(范文)
2019/08/15 职场文书
MySQL索引篇之千万级数据实战测试
2021/04/05 MySQL
粗暴解决CUDA out of memory的问题
2021/05/22 Python
Windows 11要来了?微软文档揭示Win11太阳谷 / Win10有两个不同版本
2021/11/21 数码科技
Windows server 2012搭建FTP服务器
2022/04/29 Servers
tomcat默认最大连接数及相关调整方法
2022/05/06 Servers