Vue實例要領之事宜的完成

最先

這段時刻一向在看vue的源碼,源碼異常多和雜,所以本身連繫材料和明白理出了一個主線,然後依據主線去剝離其他的一些知識點,然後將各個知識點一一進修。這裏主假如剖析的事宜體系的完成。

正文

一、相識運用體式格局

在剖析之前先相識下幾個api的運用體式格局:

vm.$on(event, callback)

  • 參數

    • {string | Array<string>} event (數組只在 2.2.0+ 中支撐)
    • {Function} callback
  • 用法$on事宜須要兩個參數,一個是監聽的當前實例上的事宜名,一個是事宜觸發的回調函數,回調函數接收的是在事宜動身的時刻分外通報的參數。
  • 例子:
vm.$on('test', function (msg) {
  console.log(msg)
})
vm.$emit('test', 'hi')
// => "hi"

vm.$once(event, callback)

$once事宜團體上來說和$on事宜的運用體式格局差不多,然則event只支撐字符串也就是說只支撐單個事宜。而且該事宜再觸發一次后就移除了監聽器。

  • 例子
vm.$once('testonce', function (msg) {
  console.log(msg)
})

vm.$off([event, callback])

  • 參數

    • {string | Array<string>} event(僅在 2.2.2+ 支撐數組)
    • {Function} [callback]
  • 用法:移除自定義事宜監聽器

    • 假如沒有供應參數,則移除一切的事宜監聽器
    • 假如只供應了事宜,則移除該事宜一切的監聽器;
    • 假如同時供應了事宜與回調,則只移除這個回調的監聽器。
  • 例子
vm.$off()
vm.$off('test')
vm.$off('test1', function (msg) {
  console.log(msg)
})
vm.$off(['test1','test2'], function (msg) {
  console.log(msg)
})

vm.$emit(event, [..args])

  • 參數

    • {string} event 要觸發的事宜名
    • [...args]可選
  • 用法

觸發當前實例上的事宜。附加參數都邑傳給監聽器回調。

  • 例子
vm.$emit('test', '觸發自定義事宜')

二、源碼剖析

事宜的初始化事情

我們在運用自定義事宜的api的時刻,一定有個處所是須要來存我們的事宜和回調的處所。在vue中大部份的初始化事情都是在core/instance/init.jsinitMixin要領中。所以我們能夠在initMixin看到initEvents要領。

// initEvents
export 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)
  }
}

上面的代碼能夠看到,在初始化Vue事宜的時刻,在vm實例上面掛載了一個_events的空對象。背面我們本身挪用的自定義事宜都存在內里。

由於vue本身在組件嵌套的時刻就有子組件運用父組件的事宜的時刻。所以就能夠經由過程updateComponentListeners要領把父組件事宜監聽器(比方click)通報給子組件。(這裏不做過量議論)

自定義事宜的掛載體式格局

自定義事宜的掛載是在eventsMixin要領中完成的。這內里將四個要領掛在Vue的原型上面。

Vue.prototype.$on
Vue.prototype.$once
Vue.prototype.$off
Vue.prototype.$emit

Vue.prototype.$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 {
      (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
}

第一個參數就是自定義事宜,由於多是數組,所以推斷假如是數組,那末就輪迴挪用this.$on要領。
假如不是數組,那末就直接向最最先定義的_events對象鳩合內里增加自定義事宜。

所以這個時刻_events對象天生的花樣也許就是下面:

vm._events={
    'test':[fn,fn...],
    'test1':[fn,fn...]
}

Vue.prototype.$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
}

這裏定義了一個on函數。接着把fn賦值給on.fn。末了在挪用的是vm.$on。這裏傳入的就是事宜名和前面定義的on函數。on函數在實行的時刻會先移除_events中對應的事宜,然後挪用fn

所以剖析下獲得的是:

vm._events={
    'oncetest':[ 
          function on(){
              vm.$off(event,on)
              fn.apply(vm,arguments)
          } ,
          ...
     ]
}

Vue.prototype.$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
    // 假如第一個參數是數組的話,那末就輪迴挪用this.$off要領
    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]
    // 沒有相干的事宜的時刻直接返回vm實例
    if (!cbs) {
      return vm
    }
    // 假如只傳入了事宜名,那末消滅該事宜名下一切的事宜。 也就是說 vm._events = {'test': null, ...}
    if (!fn) {
      vm._events[event] = null
      return vm
    }
    // 假如傳入的第二個參數為真,那末就去cbs內里遍歷,在cbs中找到和fn相稱的函數,然後經由過程splice刪除該函數。
    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 的完成

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}".`
        )
      }
    }
    // 匹配到事宜列表,該列表是一個json。
    let cbs = vm._events[event]
    if (cbs) {
      //將該json轉化成為真正的數組
      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
}

上面重要意義是:匹配到json中相干key值的value,這個value先轉換成真正的數組,再輪迴遍曆數組,傳入給的參數實行數組中的每一個函數

末了

vue中的自定義事宜重要目標是為了組件之間的通訊。由於_events對象是掛在Vue實例上的。因而每一個組件是都能夠訪問到vm._events的值的,也能夠向个中push值的。

全部自定義事宜體系呢就是在vm實例上掛載一個_events的對象,能夠明白為一個json,个中json的key值就是自定義事宜的稱號,一個key值能夠對應着多個自定義事宜,因而json中每一個key對應的value都是一個數組,每次實行事宜監聽都邑向數組中push相干的函數,終究經由過程$emit函數傳入的參數,匹配到json中響應的key,val值,從而運用給定的參數實行數組中的函數

末了的_events對象:

vm._events={
    'test1':[fn,fn,fn],
    'test2':[fn],
    'oncetest':[ 
          function on(){
              vm.$off(event,on)
              fn.apply(vm,arguments)
          },
          ... 
     ],
     ...
}
    原文作者:Charming
    原文地址: https://segmentfault.com/a/1190000014838345
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞