深入理解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 使用手册(二)
Sep 23 Javascript
JavaScript中检测变量是否存在遇到的一些问题
Nov 11 Javascript
用户代理字符串userAgent可实现的四个识别
Sep 20 Javascript
浅谈javascript alert和confirm的美化
Dec 15 Javascript
Angular动态添加、删除输入框并计算值实例代码
Mar 29 Javascript
vue 和vue-touch 实现移动端左右导航效果(仿京东移动站导航)
Apr 22 Javascript
AngularJS实用基础知识_入门必备篇(推荐)
Jul 10 Javascript
ReactNative踩坑之配置调试端口的解决方法
Jul 28 Javascript
angular4 如何在全局设置路由跳转动画的方法
Aug 30 Javascript
基于node.js express mvc轻量级框架实践
Sep 14 Javascript
如何为你的JS项目添加智能提示与类型检查详解
Mar 12 Javascript
微信小程序搭建自己的Https服务器
May 02 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
Base64在线编码解码实现代码 演示与下载
2011/01/08 PHP
PHP数组排序函数合集 以及它们之间的联系分析
2013/06/27 PHP
php用户注册信息验证正则表达式
2015/11/12 PHP
PHP获取用户客户端真实IP的解决方案
2016/10/10 PHP
filemanage功能中用到的lib.js
2007/04/08 Javascript
Open and Print a Word Document
2007/06/15 Javascript
jquery ui 1.7 ui.tabs 动态添加与关闭(按钮关闭+双击关闭)
2010/04/01 Javascript
js 控制页面跳转的5种方法
2013/09/09 Javascript
jQuery循环滚动新闻列表示例代码
2014/06/17 Javascript
Node.js的包详细介绍
2015/01/14 Javascript
JS中常用的输出方式(五种)
2016/06/12 Javascript
获取input标签的所有属性的方法
2016/06/28 Javascript
Bootstrap导航条学习使用(一)
2017/02/08 Javascript
微信小程序富文本渲染引擎的详解
2017/09/30 Javascript
antd组件Upload实现自己上传的实现示例
2018/12/18 Javascript
vue中更改数组中属性,在页面中不生效的解决方法
2019/10/30 Javascript
javascript实现前端分页效果
2020/06/24 Javascript
[03:57]2016完美“圣”典风云人物:rOtk专访
2016/12/09 DOTA
在Python中处理日期和时间的基本知识点整理汇总
2015/05/22 Python
python中__call__内置函数用法实例
2015/06/04 Python
python字典的常用操作方法小结
2016/05/16 Python
python安装numpy&amp;安装matplotlib&amp; scipy的教程
2017/11/02 Python
Vue的el-scrollbar实现自定义滚动
2018/05/29 Python
Tensorflow加载预训练模型和保存模型的实例
2018/07/27 Python
在pycharm 中添加运行参数的操作方法
2019/01/19 Python
浅谈django url请求与数据库连接池的共享问题
2019/08/29 Python
django和flask哪个值得研究学习
2020/07/31 Python
Python类成员继承重写的实现
2020/09/16 Python
CSS3中使用RGBA设置透明度的示例
2015/08/04 HTML / CSS
澳大利亚现代波西米亚风格女装网站:Bohemian Traders
2018/04/16 全球购物
潘多拉珠宝美国官方网站:Pandora US
2020/06/18 全球购物
JavaScript实现页面动态验证码的实现示例
2021/03/23 Javascript
美术专业个人自我评价
2014/01/18 职场文书
财务检查整改报告
2014/11/06 职场文书
2016中考冲刺决心书
2015/09/22 职场文书
git中cherry-pick命令的使用教程
2022/06/25 Servers