深入理解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对象的函数
Dec 22 Javascript
JS中showModalDialog 的使用解析
Apr 17 Javascript
浅析AMD CMD CommonJS规范--javascript模块化加载学习心得总结
Mar 16 Javascript
javaScript事件学习小结(四)event的公共成员(属性和方法)
Jun 09 Javascript
mongoose设置unique不生效问题的解决及如何移除unique的限制
Nov 07 Javascript
Angular实现的table表格排序功能完整示例
Dec 22 Javascript
Vue核心概念Action的总结
Jan 18 Javascript
ES6 如何改变JS内置行为的代理与反射
Feb 11 Javascript
详解js实时获取并显示当前时间的方法
May 10 Javascript
JavaScript之数组扁平化详解
Jun 03 Javascript
layui文件上传控件带更改后数据传值的方法
Sep 23 Javascript
详解JavaScript 的执行机制
Sep 18 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
解析dedecms空间迁移步骤详解
2013/05/15 PHP
php实现cc攻击防御和防止快速刷新页面示例
2014/02/13 PHP
php操作memcache缓存方法分享
2015/06/03 PHP
老版本PHP转义Json里的特殊字符的函数
2015/06/08 PHP
支持ie与FireFox的剪切板操作代码
2009/09/28 Javascript
return false;和e.preventDefault();的区别
2010/07/11 Javascript
JQuery 文本框回车跳到下一个文本框示例代码
2013/08/30 Javascript
js如何判断用户是在PC端和还是移动端访问
2014/04/24 Javascript
JS逆序遍历实现代码
2014/12/02 Javascript
ECMAScript中函数function类型
2015/06/03 Javascript
jqGrid中文文档之选项设置
2015/12/02 Javascript
深入理解jQuery中的事件冒泡
2016/05/24 Javascript
详细探究ES6之Proxy代理
2016/07/22 Javascript
AngularJs上传前预览图片的实例代码
2017/01/20 Javascript
CSS3结合jQuery实现动画效果及回调函数的实例
2017/12/27 jQuery
Vue.js 踩坑记之双向绑定
2018/05/03 Javascript
JavaScript实现的拼图算法分析
2019/02/13 Javascript
js实现微信聊天效果
2020/08/09 Javascript
python3+PyQt5实现使用剪贴板做复制与粘帖示例
2017/01/24 Python
对sklearn的使用之数据集的拆分与训练详解(python3.6)
2018/12/14 Python
python导入模块交叉引用的方法
2019/01/19 Python
利用anaconda作为python的依赖库管理方法
2019/08/13 Python
Python中remove漏删和索引越界问题的解决
2020/03/18 Python
Python2 与Python3的版本区别实例分析
2020/03/30 Python
python如何实现读取并显示图片(不需要图形界面)
2020/07/08 Python
详解Python 函数参数的拆解
2020/09/02 Python
python 5个顶级异步框架推荐
2020/09/09 Python
Python3使用Selenium获取session和token方法详解
2021/02/16 Python
Perfume’s Club澳大利亚官网:西班牙领先的在线美容店
2021/02/01 全球购物
《地震中的父与子》教学反思
2014/04/10 职场文书
校本教研活动总结
2014/07/01 职场文书
爱牙日活动总结
2014/08/29 职场文书
《围炉夜话》110句人生箴言,精辟有内涵,引人深思
2019/10/23 职场文书
微信小程序用户授权最佳实践指南
2021/05/08 Javascript
python3读取文件指定行的三种方法
2021/05/24 Python
Oracle中日期的使用方法实例
2022/07/07 Oracle