Vue源码学习之initEvents

Vue源码学习之initEvents

initLifecycle是Vue源码中core/instance/events.js下的一个函数,和上节的initLiftcycle一样,该函数也是在beforeCreate钩子之前调用,作用是初始化组件中的事件。下面让我们来进行代码分析。

1、initEvents
function initEvents (vm: Component) {
  // 存放事件的空对象
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events(初始化父组件绑定在该组件上的事件)
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}

这个函数的功能就是初始化了一个存放事件的空对象,只存放挂载在该组件上的事件。_hasHookEvent属性是表示父组件是否有将钩子函数绑定到该组件上。如果父组件有绑定事件到该组件上则调用updateComponentListeners方法,下面看一下该方法的实现。

2、updateComponentListeners
function updateComponentListeners (
  vm: Component,
  listeners: Object,
  oldListeners: ?Object
) {
  target = vm
  updateListeners(listeners, oldListeners || {}, add, remove, vm)
  target = undefined
}

这个方法没有什么内容,主要就是调用updateListeners更新Listener,比较重要的就是add和remove两个参数,这两个方法是Vue中自己实现两个添加Listener、移除Listener的方法。

3、add
function add (event, fn, once) {
  if (once) {
    target.$once(event, fn)
  } else {
    target.$on(event, fn)
  }
}

可以看到该方法主要依赖的是 o n on和 once两个vue实例上的方法,这两个方法还有 e m i t emit和 off方法都是通过一个eventsMixin方法挂载到Vue原型对象上的,下面让我们了解一下这两个方法的实现。

$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++) {
        this.$on(event[i], fn)
      }
    } else {
      // _events对象属性中存入相应事件
      (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方法主要就是将事件push到vue实例的_events对象对应事件属性下。

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

o n c e f n once方法借助了一个中间函数实现只调用一次的功能,在fn调用之前就通过 off方法卸载,再执行我们要执行的函数,这样就只调用一次了

回到刚才的add方法,可以发现add方法就是通过调用 o n on和 once方法进行添加事件。

4、remove
function remove (event, fn) {
  target.$off(event, fn)
}

remove的本质是通过 o f f off方法卸载事件,让我们直接看 off方法。

$off
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    const vm: Component = this
    // all(不传参数为清空全部事件,直接将_events对象置空)
    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]
    if (!cbs) {
      return vm
    }
    // 如果只传入第一个参数,将该属性下的所有事件清空
    if (!fn) {
      vm._events[event] = null
      return vm
    }
    if (fn) {
      // specific handler
      let cb
      let i = cbs.length
      while (i--) {
        cb = cbs[i]
        // cb.fn是使用$once注册的情况
        if (cb === fn || cb.fn === fn) {
          cbs.splice(i, 1)
          break
        }
      }
    }
    return vm
  }

通过代码可以看出$off有三种用法,第一种:不传参数,将所有事件清空;第二种:只传第一个参数,将_events对象下的该属性事件数组清空;第三种:传入两个参数,清除某个特定事件。

$emit

介绍完了 o n on, once, o f f off肯定还要介绍一下 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++) {
        try {
          cbs[i].apply(vm, args)
        } catch (e) {
          handleError(e, vm, `event handler for "${event}"`)
        }
      }
    }
    return vm
  }

忽略前面的一大段关于环境的判断,可以看出$emit方法就是直接触发vue实例下的_events对象中的方法。

5、updateListeners
function updateListeners (
  on: Object,
  oldOn: Object,
  add: Function,
  remove: Function,
  vm: Component
) {
  let name, def, cur, old, event
  for (name in on) {
    def = cur = on[name]
    old = oldOn[name]
    event = normalizeEvent(name)
    /* istanbul ignore if */
    if (__WEEX__ && isPlainObject(def)) {
      cur = def.handler
      event.params = def.params
    }
    if (isUndef(cur)) {
      process.env.NODE_ENV !== 'production' && warn(
        `Invalid handler for event "${event.name}": got ` + String(cur),
        vm
      )
    } else if (isUndef(old)) {
      if (isUndef(cur.fns)) {
        cur = on[name] = createFnInvoker(cur)
      }
      add(event.name, cur, event.once, event.capture, event.passive, event.params)
    } else if (cur !== old) {
      old.fns = cur
      on[name] = old
    }
  }
  for (name in oldOn) {
    if (isUndef(on[name])) {
      event = normalizeEvent(name)
      remove(event.name, oldOn[name], event.capture)
    }
  }
}

简单的说就是往vue实例中添加on对象内事件,移除oldOn下的事件。

猜你喜欢

转载自blog.csdn.net/qq_34179086/article/details/88081618