深入理解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 相关文章推荐
IE8 引入跨站数据获取功能说明
Jul 22 Javascript
jQuery textarea的长度进行验证
May 06 Javascript
js页面滚动时层智能浮动定位实现(jQuery/MooTools)
Aug 23 Javascript
js自动闭合html标签(自动补全html标记)
Oct 04 Javascript
利用jQuery的deferred对象实现异步按顺序加载JS文件
Mar 17 Javascript
javascript HTML5 canvas实现打砖块游戏
Jun 18 Javascript
javascript代码调试之console.log 用法图文详解
Sep 30 Javascript
javascript数据类型详解
Feb 07 Javascript
微信小程序左右滑动切换页面详解及实例代码
Feb 28 Javascript
Vue.js 时间转换代码及时间戳转时间字符串
Oct 16 Javascript
Electron 打包问题:electron-builder 下载各种依赖出错(推荐)
Jul 09 Javascript
原生JS实现音乐播放器的示例代码
Feb 25 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
DOTA2 无惧惊涛骇浪 昆卡大型水友攻略
2020/04/20 DOTA
法压式咖啡之制作法
2021/03/03 冲泡冲煮
async和DOM Script文件加载比较
2014/07/20 PHP
php动态添加url查询参数的方法
2015/04/14 PHP
YII2框架中使用yii.js实现的post请求
2017/04/09 PHP
phpStudy2016 配置多个域名期间遇到的问题小结
2017/10/19 PHP
Javascript里使用Dom操作Xml
2007/01/22 Javascript
关于javascript 回调函数中变量作用域的讨论
2009/09/11 Javascript
一个简单的js渐显(fadeIn)渐隐(fadeOut)类
2010/06/19 Javascript
原生js实现改变随意改变div属性style的名称和值的结果
2013/09/26 Javascript
js校验表单后提交表单的三种方法总结
2014/02/28 Javascript
巧用replace将文字表情替换为图片
2014/04/17 Javascript
JavaScript实现的简单拖拽效果
2015/06/01 Javascript
JQuery的常用选择器、过滤器、方法全面介绍
2016/05/25 Javascript
Angular实现的日程表功能【可添加及隐藏显示内容】
2017/12/27 Javascript
微信小程序实现图片上传放大预览删除代码
2020/06/28 Javascript
jQuery实现的点击图片居中放大缩小功能示例
2019/01/16 jQuery
python常规方法实现数组的全排列
2015/03/17 Python
python解析xml文件实例分析
2015/05/27 Python
Python三种遍历文件目录的方法实例代码
2018/01/19 Python
python 接口测试response返回数据对比的方法
2018/02/11 Python
Python二叉树的镜像转换实现方法示例
2019/03/06 Python
django一对多模型以及如何在前端实现详解
2019/07/24 Python
python+tkinter实现学生管理系统
2019/08/20 Python
Numpy的简单用法小结
2019/08/28 Python
使用jupyter notebook将文件保存为Markdown,HTML等文件格式
2020/04/14 Python
德国PC硬件网站:CASEKING
2016/10/20 全球购物
加拿大时装零售商:Influence U
2018/12/22 全球购物
C#中类(class)与结构(struct)的异同
2013/11/03 面试题
在c#中using和new这两个关键字有什么意义
2013/05/19 面试题
策划助理岗位职责
2013/11/18 职场文书
机械机修工岗位职责
2014/08/03 职场文书
党员群众路线对照检查材料
2014/08/31 职场文书
教师学习三严三实心得体会
2014/10/13 职场文书
Java移除无效括号的方法实现
2021/08/07 Java/Android
python数字图像处理之图像自动阈值分割示例
2022/06/28 Python