美文网首页
vue源码分析(二十一)Vue事件系统($on、$once、$o

vue源码分析(二十一)Vue事件系统($on、$once、$o

作者: vue爱好者 | 来源:发表于2020-04-22 08:25 被阅读0次

    我们在vue源码分析(三)解密new Vue()之前做了哪些不为人知工作(第一篇)的时候曾经提到过 eventsMixin函数。

    这个函数主要是在vue的“prototype”原型对象上面挂载了几个自定义事件“$on”、“$once”、“$off”、“$emit”。

    我们接下来就具体的看看这些函数的具体代码:

    $on

     Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
        const vm: Component = this
        if (Array.isArray(event)) {
          for (let i = 0, l = event.length; i < l; i++) {
            vm.$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
          if (hookRE.test(event)) {
            vm._hasHookEvent = true
          }
        }
        return vm
      }
    

    可以看到$on接收两个参数 eventfn,分别是事件名称和事件处理函数,并且事件名称可以为数组,就是相当于不同的事件绑定了同一个事件处理函数。

    (vm._events[event] || (vm._events[event] = [])).push(fn)
    

    看上面一行代码大家可以看到,定义重复的事件名称是分开的,不会合并为一个事件处理。

    // const hookRE = /^hook:/
    if (hookRE.test(event)) {
      vm._hasHookEvent = true
    }
    

    这行代码就是当你的事件名称是用 hook :开头的话就会把 _hasHookEvent 变量设置为了true。我把这个叫做 ‘钩子事件’,就是生命周期钩子被调用的时候,就会触发对应的钩子事件,比如($on('hook:created', fn)、$on('hook:mounted',fn))。

    $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
      }
    

    可以看到这里定义了一个局部的 on函数,用户传进来的fn函数,被挂载在了on的构造函数的fn属性上面,然后调用了$on,就相当于调用$emit执行的函数是on而不是直接执行fnon函数里面首先是清理了绑定事件$off,然后在执行fn,并且调用了apply改变了this指向,相当于fn是不是箭头函数都一样了。

    $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
        if (Array.isArray(event)) {
          for (let i = 0, l = event.length; i < l; i++) {
            vm.$off(event[i], fn)
          }
          return vm
        }
        // specific event
        const cbs = vm._events[event]
        if (!cbs) {
          return vm
        }
        if (!fn) {
          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
      }
    

    可以看到$off也是接收两个参数,但是都是可选。
    首先看到判断是否有参数,如果么有用的话,就把 _events设置为空,就相当于把所有的事件清除了。
    然后就判是event是否是数组,然后递归调用$off

     if (!cbs) {
       return vm
     }
    

    如果事件名称不存在就,return vm,下面不再执行了。

    if (!fn) {
       vm._events[event] = null
       return vm
     }
    

    如果fn事件不存在,则移除该事件所有的监听器。

    let cb
        let i = cbs.length
        while (i--) {
          cb = cbs[i]
          if (cb === fn || cb.fn === fn) {
            cbs.splice(i, 1)
            break
          }
      }
    

    如果fn参数和$on绑定的事件cb相等,或者与cb.fn相等,就从数组移除。

    cb.fn就是在$once里面定义的on.fn = fn

    $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
          // 处理$emit函数的第二个参数,就是取arguments[1]
          const args = toArray(arguments, 1)
          const info = `event handler for "${event}"`
          for (let i = 0, l = cbs.length; i < l; i++) {
            invokeWithErrorHandling(cbs[i], vm, args, vm, info)
          }
        }
        return vm
      }
    

    可以看到如果在开发环境的话,传进来的event事件名称,先进行一些小写处理,小写处理过后的事件名称lowerCaseEventevent不相等并且事件处理函数又存在,那就会提示tip

    let cbs = vm._events[event]
    

    获取所有的event事件处理函数。最后调用invokeWithErrorHandling函数,触发$on或者$once绑定的事件处理函数。
    最后就是返回vm

    相关文章

      网友评论

          本文标题:vue源码分析(二十一)Vue事件系统($on、$once、$o

          本文链接:https://www.haomeiwen.com/subject/nkqjuhtx.html