Vue2.x源码学习笔记(九)——实例方法(事件篇)

156 阅读1分钟

在Vue初始化(_init)时会调用initEvents函数来对事件进行初始化。initEvents中在Vue实例上创建了一个空对象用来存放事件

 vm._events = Object.create(null)

vue的事件系统定义在/src/core/instance/events.js中的eventsMixin函数里。

vm.$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)
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }

第一个参数代表了事件名,如果传入的是一个数组,那就新循环这个数组递归调用$on。将注册的事件和回调以键值对的形式存储到 vm._events 对象中:vm._events = { eventA: [fn1, ...] }。

vm.$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]) {
	//报错
      }
    }
    let cbs = vm._events[event]
    if (cbs) {
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      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
  }

从 vm._events 对象上根据事件名拿到当前事件的回调函数数组。获得参数后,循环回调函数数组,通过invokeWithErrorHandling包裹后调用每一个函数并捕获异常。

vm.$off

  Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    const vm: Component = this
    if (!arguments.length) {
      vm._events = Object.create(null)
      return vm
    }
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$off(event[i], fn)
      }
      return vm
    }
    const cbs = vm._events[event]
    if (!cbs) {
      return vm
    }
    if (!fn) {
      vm._events[event] = null
      return vm
    }
    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时不传递任何参数,则会清空整个事件对象(vm._events)。

如果第一个参数是一个数组,怎会循环这个数组,递归调用$off。

如果参数只传了一个事件名,则将事件对象中这个事件的函数数组清空。

如果参数传递了一个事件名,并切第二个参数是一个具体函数,那么循环这个事件名对应的函数数组,找到对应的函数并移除。

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

$once只调用一次,实际还是调用了$on方法,在执行回调函数时会先移除本身,然后再执行第二个参数对应的函数