Skip to content

Vue 事件机制 #17

@Lindysen

Description

@Lindysen

由Vue的API可知

vm.$on监听当前实例上的自定义事件

vm.$once监听当前实例上的事件,但是只触发一次,在第一次触发后就移除监听器

vm.$off移除自定义事件监听器

  1. 不传任何参数则移除当前实例上所有的事件监听器
  2. 如果只提供了事件,则移除该事件所有的监听器
  3. 如果同时提供了事件与回调,则只移除这个回调的监听器

vm.$emit触发当前实例上的事件

那么我们来看看源码吧

 vm._events = Object.create(null) // vm._events初始化为一个对象
 vm._hasHookEvent = false 
 // 标志位 是否监听了生命周期钩子事件 beforeCreate等 
 
  
  
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++) {
        this.$on(event[i], fn)
      }
    } else {
    // 在vm._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
  }
  
  // 只监听一次
  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++) {
        this.$off(event[i], fn)
      }
      return vm
    }
    // specific event
    const cbs = vm._events[event]
    if (!cbs) {
      return vm
    }
    if (arguments.length === 1) {
      vm._events[event] = null // 移除特定事件监听
      return vm
    }
    if (fn) { // 移除特定事件的特定处理函数
      // 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) {
    // toArray函数的作用时转换一个类数组对象为真正的数组
      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
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions