摘要:例子觸發自定義事件二源碼分析事件的初始化工作我們在使用自定義事件的的時候,肯定有個地方是需要來存我們的事件和回調的地方。這里不做過多討論自定義事件的掛載方式自定義事件的掛載是在方法中實現的。
開始
這段時間一直在看vue的源碼,源碼非常多和雜,所以自己結合資料和理解理出了一個主線,然后根據主線去剝離其他的一些知識點,然后將各個知識點逐一學習。這里主要是分析的事件系統的實現。
正文 一、了解使用方式在分析之前先了解下幾個api的使用方式:
vm.$on(event, callback)
參數:
{string | Array
{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
{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.js的initMixin方法中。所以我們能夠在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, 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, 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) }, ... ], ... }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/94938.html
摘要:例子觸發自定義事件二源碼分析事件的初始化工作我們在使用自定義事件的的時候,肯定有個地方是需要來存我們的事件和回調的地方。這里不做過多討論自定義事件的掛載方式自定義事件的掛載是在方法中實現的。 開始 這段時間一直在看vue的源碼,源碼非常多和雜,所以自己結合資料和理解理出了一個主線,然后根據主線去剝離其他的一些知識點,然后將各個知識點逐一學習。這里主要是分析的事件系統的實現。 正文 一、...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關注公眾號也可以吧原理源碼版之綁定組件自定義事件組件 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于...
摘要:本文是小羊根據文檔進行解讀的第一篇文章,主要內容涵蓋的基礎部分的知識的,文章順序基本按照官方文檔的順序,每個知識點現附上代碼,然后根據代碼給予個人的一些理解,最后還放上在線編輯的代碼以供練習和測試之用在最后,我參考上的一篇技博,對進行初入的 本文是小羊根據Vue.js文檔進行解讀的第一篇文章,主要內容涵蓋Vue.js的基礎部分的知識的,文章順序基本按照官方文檔的順序,每個知識點現附上代...
閱讀 1228·2021-11-15 11:37
閱讀 2246·2021-09-30 09:55
閱讀 4483·2021-09-22 15:51
閱讀 3741·2021-09-22 15:46
閱讀 2766·2019-08-30 15:52
閱讀 423·2019-08-29 16:20
閱讀 2889·2019-08-29 15:12
閱讀 1130·2019-08-26 18:27