深入理解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 相关文章推荐
js 分栏效果实现代码
Aug 29 Javascript
JQuery入门——移除绑定事件unbind方法概述及应用
Feb 05 Javascript
jQuery.event兼容各浏览器的event详细解析
Dec 18 Javascript
原生javascript获取元素样式
Dec 31 Javascript
浅析javascript中的DOM
Mar 01 Javascript
javascript实现画不相交的圆
Apr 07 Javascript
JS实现复制内容到剪贴板功能
Feb 05 Javascript
jQuery响应滚动条事件功能示例
Oct 14 jQuery
Angular实现类似博客评论的递归显示及获取回复评论的数据
Nov 06 Javascript
vue使用v-for实现hover点击效果
Sep 29 Javascript
layui自定义验证,用ajax查询后台是否有重复数据,form.verify的例子
Sep 06 Javascript
解决vue admin element noCache设置无效的问题
Nov 12 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中使用json数据格式定义字面量对象的方法
2014/08/20 PHP
ThinkPHP框架实现FTP图片上传功能示例
2019/04/08 PHP
PHP7新特性
2021/03/09 PHP
firefox下jquery iframe刷新页面提示会导致重复之前动作
2012/12/17 Javascript
检查输入的是否是数字使用keyCode配合onkeypress事件
2014/01/23 Javascript
在JS数组特定索引处指定位置插入元素的技巧
2014/08/24 Javascript
jQuery下拉友情链接美化效果代码分享
2015/08/26 Javascript
Javascript中匿名函数的调用与写法实例详解(多种)
2016/01/26 Javascript
jQuery UI仿淘宝搜索下拉列表功能
2017/01/10 Javascript
Jquery根据浏览器窗口改变调整大小的方法
2017/02/07 Javascript
加载 vue 远程代码的组件实例详解
2017/11/20 Javascript
Vue中v-show添加表达式的问题(判断是否显示)
2018/03/26 Javascript
详解Puppeteer前端自动化测试实践
2019/02/21 Javascript
JavaScript中的事件与异常捕获详析
2019/02/24 Javascript
vue指令做滚动加载和监听等
2019/05/26 Javascript
微信小程序实现多选框全选与反全选及购物车中删除选中的商品功能
2019/12/17 Javascript
Vue-axios-post数据后端接不到问题解决
2020/01/09 Javascript
微信小程序实现音频文件播放进度的实例代码
2020/03/02 Javascript
Python的类实例属性访问规则探讨
2015/01/30 Python
Python脚本实现网卡流量监控
2015/02/14 Python
python检查字符串是否是正确ISBN的方法
2015/07/11 Python
CentOS 6.X系统下升级Python2.6到Python2.7 的方法
2016/10/12 Python
Python实现嵌套列表及字典并按某一元素去重复功能示例
2017/11/30 Python
将python安装信息加入注册表的示例
2019/11/20 Python
浅谈ROC曲线的最佳阈值如何选取
2020/02/28 Python
如何使用Python自动生成报表并以邮件发送
2020/10/15 Python
优秀学生事迹材料
2014/02/08 职场文书
医学生求职信
2014/07/01 职场文书
优秀员工事迹材料
2014/12/20 职场文书
2015新学期校长寄语(3篇)
2015/03/25 职场文书
2015年纪念“卢沟桥事变”78周年活动方案
2015/05/06 职场文书
运动会观后感
2015/06/09 职场文书
新学期主题班会
2015/08/17 职场文书
中国文明网2015年“向国旗敬礼”活动网上签名寄语
2015/09/24 职场文书
《青山不老》教学反思
2016/02/22 职场文书
导游词之安徽巢湖
2019/12/26 职场文书