• Vue.js源码——事件机制


    写在前面

    因为对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$once$off$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
      }
    

    关于

    作者:染陌

    Email:answershuto@gmail.com or answershuto@126.com

    Github: https://github.com/answershuto

    Blog:http://answershuto.github.io/

    知乎主页:https://www.zhihu.com/people/cao-yang-49/activities

    知乎专栏:https://zhuanlan.zhihu.com/ranmo

    掘金: https://juejin.im/user/58f87ae844d9040069ca7507

    osChina:https://my.oschina.net/u/3161824/blog

    转载请注明出处,谢谢。

    欢迎关注我的公众号

  • 相关阅读:
    Div在BOdy中居中
    c_lc_填充每个节点的下一个右侧节点指针 I~II(递归)
    c_pat_哈密顿回路 & 最大点集 & 是否是旅行商路径 & 欧拉路径 & 最深的根(邻接矩阵存图)
    c_lc_二叉搜索树的最近公共祖先 & 二叉树的最近公共祖先(利用性质 | 从p,q开始存储每个结点的父亲)
    c_pat_树题大杂烩(利用性质)
    现在的我,理解了这种「激情」
    b_pat_排成最小的数字 & 月饼(字符串拼接比较a+b<b+a)
    c_lc_二叉搜索树中的众数(中序遍历+延迟更新前驱结点)
    b_pat_分享 & 链表排序 & 链表去重(链表模拟)
    b_pat_弹出序列(栈模拟)
  • 原文地址:https://www.cnblogs.com/answershuto/p/7599321.html
Copyright © 2020-2023  润新知