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

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

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

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

$on

 Vue.prototype.$on = function (event: string | Array, 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, 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、$off、$emit))