大家都能懂的Vue源碼系列—09—initEvents

上篇文章中,我們重要講了initLiftcycle要領,它的作用是初始化vm實例中和生命周期相干的屬性。本日為人人引見另一個要領——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)
  }
}

先看第一行

vm._events = Object.create(null)

該行代碼創建了一個原型為null的空對象,並把它賦值給vm實例的_events屬性。關於Object.create,不明白的同硯能夠點這裏檢察。關於vm._events,許多博文中提到該屬性就是籠統的說是用來寄存事宜的對象,那末究竟寄存什麼事宜呢?vm的一切事宜都寄存在裏面?顯然是不對的,那詳細是什麼,我們來看下面的例子。
html部份:

  <div id="app">
    <child
      @hook:created="hookFromParent"
      @hover="hoverFromParent"
      :msg-from-father="msg" 
      :hit-from-father="hit"
    >
    </child>
  </div>

js部份

  const childComponent = Vue.component('child', {
      template: '<div><p @click="showMsgFromSon">{{msgFromFather}} and {{hitFromFather}}</p></div>',
      data: function () {
        return {
          childMsg: 'Hello, I am Child'
        }
      },
      props: ['msgFromFather', 'hitFromFather'],
      methods: {
        showMsgFromSon () {
          console.log('Hello Vue from son')
        }
      },
      mounted () {
        console.log('child mounted')
      }
    })
    const app = new Vue({
      el: '#app',
      data: function () {
        return {
          msg: 'Hello Chris, I am your father',
          hit: 'I will hit you if you do not study',
        }
      },
      components: {
        childComponent
      },
      methods: {
        hoverFromParent () {
          console.log('attch event')
        },
        hookFromParent () {
          console.log('attch hook')
        }
      }
    })

下圖示意的是上述demo中vm._events屬性的值

《大家都能懂的Vue源碼系列—09—initEvents》

上面例子中,child組件上除了父組件綁定的要領以外,其組件內部另有showMsgFromSon和mounted鈎子要領,然則這兩個要領都沒有出如今_events屬性中。綜上可知,vm._events示意的是父組件綁定在當前組件上的事宜。
接下來看代碼

vm._hasHookEvent = false

這行代碼把我們vm實例上的_hasHookEvent屬性設置為false。該屬性示意父組件是不是經由歷程”@hook:”把鈎子函數綁定在當前組件上。該用法能夠在上個demo中找到,經由歷程以下體式格局完成綁定。

@hook:鈎子稱號="綁定的函數"

繼承回到源碼中

// init parent attached events
const listeners = vm.$options._parentListeners

從英文詮釋中,我們曉得這行代碼的作用是初始化父組件增加的事宜。那詳細是什麼意義呢?經由歷程追蹤vm.$options._parentListeners的賦值歷程(這個歷程有點龐雜,在以後講雙向綁定和假造dom的時刻會說到),我們曉得vm.$options._parentListeners實在和上面的_events一樣,都是用來示意父組件綁定在當前組件上的事宜。(固然照樣略有點差別,這個以後會解說)假如存在這些綁定的事宜,那末就實行下面代碼

 if (listeners) {
   updateComponentListeners(vm, listeners)
 }

假如事宜存在,則挪用updateComponentListeners更新這些要領。

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

來看updateComponentListeners要領的源碼

target = vm

這行代碼的重要作用是保存對vm實例的援用,在實行updateListeners要領時能訪問到實例對象,並實行add和remove要領。

updateListeners(listeners, oldListeners || {}, add, remove, vm)

在研討updateListeners源碼之前,我們先來相識一下傳入的這幾個參數。listeners我們前面說過,是父組件綁定在當前組件上的事宜對象,oldListeners示意當前組件上舊的事宜對象,vm是vue實例對象。這三個沒什麼好說的,我們詳細來講講別的兩個參數add和remove。

add要領

add要領源碼以下:

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

假如第三個參數once為true,則實行vue.$once要領,不然實行vue.$on要領。我們先來看vue.$on

vue.$on要領

為何要先講$on要領,因為$once要領中也須要用到$on,在看$on源碼之前,我們先來看看官方文檔里對它的定義。

監聽當前實例上的自定義事宜。事宜能夠由vm.$emit觸發。回調函數會吸收一切傳入事宜觸發函數的分外參數。

曉得了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 {
      (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
  }

else之前的代碼都很簡樸,先緩存this,假如傳入的事宜是事宜數組的話,則離別對數組內的每一項挪用$on綁定事宜。接下來重點看看else塊內的代碼。

(vm._events[event] || (vm._events[event] = [])).push(fn)

我們曉得_events是示意直接綁定在組件上的事宜,假如是經由歷程$on新增加的事宜(也相當於直接綁定在組件上的事宜),我們也要把事宜和回調要領傳入到_events對象中。
回到源碼中

// optimize hook:event cost by using a boolean flag marked at registration
// instead of a hash look
if (hookRE.test(event)) {
  vm._hasHookEvent = true
}

關於這句代碼的詮釋,網上許多文章說的都是相似下面的話

這個bool標誌位來表明是不是存在鈎子,而不須要經由歷程哈希表的要領來查找是不是有鈎子,如許做能夠削減不必要的開支,優化機能。

這句話除了是翻譯原文詮釋以外,還存在顯著的毛病,這個tag不是表明是不是存在鈎子,而是示意是不是運用下面的體式格局綁定鈎子。
假如是以下情勢綁定的鈎子,則_hasHookEvent屬性為true。

<child
  @hook:created="hookFromParent"
>

而像下面這類情勢,它也存在鈎子函數,然則它的_hasHookEvent就是false。

    const childComponent = Vue.component('child', {
      ...
      created () {
        console.log('child created')
      }
    })

所以_hasHookEvent不是示意是不是存在鈎子,它示意的是父組件有無直接綁定鈎子函數在當前組件上。說這麼多,只是願望人人盡量的少被誤導。那末,那句詮釋究竟是什麼意義呢?我們能夠從callHook的源碼中來尋覓答案。

export function callHook (vm: Component, hook: string) {
  const handlers = vm.$options[hook]
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) {
      try {
        handlers[i].call(vm)
      } catch (e) {
        handleError(e, vm, `${hook} hook`)
      }
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
}

當前實例的鈎子函數假如是經由歷程父組件的:hook體式格局來指定的,那末它在實行鈎子函數的回調要領時就是直接觸發vm.$emit來實行。(這類體式格局相似於dom中的addEventListener監聽事宜和dispatchEvent觸發事宜)
假如不是上面這類要領指定的鈎子函數,就須要實行callhook源碼上半部份的代碼邏輯。找到vm實例上的鈎子函數,然後實行綁定在它上面的回調。至於實行效力的題目,沒有去研討過,然則原文詮釋里都說了是優化鈎子,那末證實第一種要領實行效力應當是優於第二種要領。
我們回到$on的源碼中,末了是返回vm實例對象。

return vm

如今我們曉得了vm.$on要領重要就是把傳入的要領給push到_events屬性里,輕易以後被$emit挪用。

vm.$once

講過了vm.$on的重要作用以後,我們接着來剖析vm.$once的源碼,先看文檔中關於vm.$once的定義。

監聽一個自定義事宜,然則只觸發一次,在第一次觸發以後移除監聽器。

相識了vm.$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
  }

連繫上面的定義和之前講的vm.$on要領,我們應當比較輕易明白解$once了,它和$on要領的中心區分重要在on要領

   function on () {
      vm.$off(event, on)
      fn.apply(vm, arguments)
    }

on要領包裝了event的回調事宜,這是on和once最實質的區分,當觸發once綁定的回調時刻,實行on要領,先挪用$off要領(這個要領是移除監聽的要領,我們待會兒就會講)移除監聽,然後再實行回調函數。如許就完成了只觸發一次的功用,講到這裏,add要領中一切的內容就已講完了。
因為文章篇幅的緣由,其他關於initEvents的內容我們下篇文章繼承講,重要有$off,$emit和updateListeners相干的完成。敬請期待。

    原文作者:淼淼真人
    原文地址: https://segmentfault.com/a/1190000014957450
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞