上篇文章中,我們重要講了initLiftcycle要領,它的作用是初始化vm實例中和生命周期相干的屬性。本日為人人引見另一個要領——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部份:
@hook:created="hookFromParent"
@hover="hoverFromParent"
:msg-from-father="msg"
:hit-from-father="hit"
>
js部份
const childCompOnent= Vue.component('child', {
template: '{{msgFromFather}} and {{hitFromFather}}
',
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屬性的值
上面例子中,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, fn: Function): Component {
const vm: CompOnent= this
if (Array.isArray(event)) {
for (let i = 0, l = event.length; 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。
@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 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相干的完成。敬請期待。