Vue.js原始碼——事件機制

染陌同學發表於2019-03-03

寫在前面

因為對Vue.js很感興趣,而且平時工作的技術棧也是Vue.js,這幾個月花了些時間研究學習了一下Vue.js原始碼,並做了總結與輸出。
文章的原地址:github.com/answershuto…
在學習過程中,為Vue加上了中文的註釋github.com/answershuto…,希望可以對其他想學習Vue原始碼的小夥伴有所幫助。
可能會有理解存在偏差的地方,歡迎提issue指出,共同學習,共同進步。

Vue事件API

眾所周知,Vue.js為我們提供了四個事件API,分別是$on$once$off$emit

初始化事件

初始化事件在vm上建立一個_events物件,用來存放事件。_events的內容如下:

{
    eventName: [func1, func2, func3]
}複製程式碼

存放事件名以及對應執行方法。

/*初始化事件*/
export function initEvents (vm: Component) {
  /*在vm上建立一個_events物件,用來存放事件。*/
  vm._events = Object.create(null)
  /*這個bool標誌位來表明是否存在鉤子,而不需要通過雜湊表的方法來查詢是否有鉤子,這樣做可以減少不必要的開銷,優化效能。*/
  vm._hasHookEvent = false
  // init parent attached events
  /*初始化父元件attach的事件*/
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}複製程式碼

$on

$on方法用來在vm例項上監聽一個自定義事件,該事件可用$emit觸發。

  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    const vm: Component = this

    /*如果是陣列的時候,則遞迴$on,為每一個成員都繫結上方法*/
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        this.$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
      /*這裡在註冊事件的時候標記bool值也就是個標誌位來表明存在鉤子,而不需要通過雜湊表的方法來查詢是否有鉤子,這樣做可以減少不必要的開銷,優化效能。*/
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }複製程式碼

$once

$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
  }複製程式碼

$off

$off用來移除自定義事件

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
    /*如果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]
    /*Github:https://github.com/answershuto*/
    /*本身不存在該事件則直接返回*/
    if (!cbs) {
      return vm
    }
    /*如果只傳了event引數則登出該event方法下的所有方法*/
    if (arguments.length === 1) {
      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
  }複製程式碼

$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++) {
        cbs[i].apply(vm, args)
      }
    }
    return vm
  }複製程式碼

關於

作者:染陌

Email:answershuto@gmail.com or answershuto@126.com

Github: github.com/answershuto

Blog:answershuto.github.io/

知乎主頁:www.zhihu.com/people/cao-…

知乎專欄:zhuanlan.zhihu.com/ranmo

掘金: juejin.im/user/58f87a…

osChina:my.oschina.net/u/3161824/b…

轉載請註明出處,謝謝。

歡迎關注我的公眾號

相關文章