text: /* @flow */
  
  import {
    tip,
    toArray,
    hyphenate,
    formatComponentName,
    invokeWithErrorHandling
  } from '../util/index'
  import { updateListeners } from '../vdom/helpers/index'
  
  export function initEvents (vm: Component) {
    // 初始化_events\_hasHookEvent = false
    vm._events = Object.create(null)
    vm._hasHookEvent = false
    // init parent attached events
    // 获取当前组件所派发给父组件监听的事件 如<child @child-click></child> 
    const listeners = vm.$options._parentListeners
    if (listeners) {
      updateComponentListeners(vm, listeners)
    }
  }
  
  let target: any
  
  function add (event, fn) {
    target.$on(event, fn)
  }
  
  function remove (event, fn) {
    target.$off(event, fn)
  }
  
  function createOnceHandler (event, fn) {
    const _target = target
    return function onceHandler () {
      const res = fn.apply(null, arguments)
      if (res !== null) {
        _target.$off(event, onceHandler)
      }
    }
  }
  
  export function updateComponentListeners (
    vm: Component,
    listeners: Object,
    oldListeners: ?Object
  ) {
    target = vm
    updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
    target = undefined
  }
  
  export function eventsMixin (Vue: Class<Component>) {
    const hookRE = /^hook:/
    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
    }
  
    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
    }
  
    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
    }
  
    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)
        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
    }
  }
  