摘要:回調函數會接收所有傳入事件觸發函數的額外參數。這種方式類似于中的監聽事件和觸發事件如果不是上面這種方法指定的鉤子函數,就需要執行源碼上半部分的代碼邏輯。
上篇文章中,我們主要講了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部分:
js部分
const childComponent = Vue.component("child", { template: "", 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") } } }){{msgFromFather}} and {{hitFromFather}}
下圖表示的是上述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 < 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。
而像下面這種形式,它也存在鉤子函數,但是它的_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相關的實現。敬請期待。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/95169.html
摘要:主要是通過為我們屬性添加一些自定義的行為。方法用來初始化一些生命周期相關的屬性,以及為等屬性賦值,來看源碼。名稱說明指定已創建的實例之父實例,在兩者之間建立父子關系。一個對象,持有已注冊過的所有子組件。 上篇文章,我們講了vm._renderProxy相關的內容。主要是通過Proxy為我們vm屬性添加一些自定義的行為。今天我們回到init方法中,為大家講解initLifecycle。i...
摘要:果然我們找到了的構造函數定義。告訴你是一個構造函數,需要用操作符去調用。在深入方法之前,我們先把目光移到文件里在的構造函數定義之后,有一系列方法會被立即調用。下篇博文主要介紹相關的內容,涉及到原型鏈和構造函數以及部分的實現,敬請期待 上篇博文中說到了Vue源碼的目錄結構是什么樣的,每個目錄的作用我們應該也有所了解。我們知道core/instance目錄主要是用來實例化Vue對象,所以我...
摘要:閱讀的源碼,或者說閱讀一個框架的源碼,了解它的目錄結構都是很有幫助的。人人都能懂的源碼系列文章將會詳細的介紹源碼的方方面面。 閱讀Vue的源碼,或者說閱讀一個框架的源碼,了解它的目錄結構都是很有幫助的。下面我們來看看Vue源碼的目錄結構。showImg(https://segmentfault.com/img/bV8fLS?w=598&h=654); Vue各目錄簡介 下圖是Vue各個...
摘要:上一篇文章中說道,函數要分兩種情況進行說明,第一種是為基礎構造器的情況,這個已經向大家介紹過了,今天這篇文章主要介紹第二種情況,是創建的子類。表示的是當前構造器上新增的,表示的是當前構造器上新增的封裝。 上一篇文章中說道,resolveConstructorOptions函數要分兩種情況進行說明,第一種是Ctor為基礎構造器的情況,這個已經向大家介紹過了,今天這篇文章主要介紹第二種情況...
摘要:上一篇文章中說道,函數要分兩種情況進行說明,第一種是為基礎構造器的情況,這個已經向大家介紹過了,今天這篇文章主要介紹第二種情況,是創建的子類。表示的是當前構造器上新增的,表示的是當前構造器上新增的封裝。 上一篇文章中說道,resolveConstructorOptions函數要分兩種情況進行說明,第一種是Ctor為基礎構造器的情況,這個已經向大家介紹過了,今天這篇文章主要介紹第二種情況...
閱讀 1600·2021-11-16 11:44
閱讀 7498·2021-09-22 15:00
閱讀 4533·2021-09-02 10:20
閱讀 1962·2021-08-27 16:20
閱讀 2402·2019-08-26 14:00
閱讀 2916·2019-08-26 11:44
閱讀 1648·2019-08-23 18:33
閱讀 1880·2019-08-22 17:28