Vue 組件事件觸發(fā)和監(jiān)聽實現(xiàn)源碼解析
正文
通常我們在使用Vue
的時候,會使用$emit
和$on
來觸發(fā)和監(jiān)聽事件,但是有沒有思考是如何實現(xiàn)的呢?
今天帶來的是一個微型的事件觸發(fā)的庫,借它們的源碼來簡單初步了解Vue
的事件觸發(fā)和監(jiān)聽的實現(xiàn)。
mitt
使用TypeScript
編寫,tiny-emitter
使用原生ES5
編寫,兩者對比tiny-emitter
功能稍微豐富一寫,所以直接看tiny-emitter
就好了。
Vue 的事件觸發(fā)和監(jiān)聽
我沒有標題黨,先來看一下Vue
的組件事件怎么使用的:
// 父組件 <template> <div> <child @my-event="handleMyEvent"></child> </div> </template> <script> import Child from './Child.vue' export default { components: { Child }, methods: { handleMyEvent (event) { console.log(event) } } } </script> // 子組件 <template> <div> <button @click="handleClick">click</button> </div> </template> <script> export default { methods: { handleClick () { this.$emit('my-event', { a: 1 }) } } } </script>
這個代碼大家都熟,簡化之后其實可以理解成這樣的:
// 父組件 import Child from './Child.js' const child = new Child() child.$on('my-event', (event) => { console.log(event) }); // 子組件 export default class Child extends Emitter { constructor () { super(); } handleClick () { this.$emit('my-event', { a: 1 }) } }
這里可以看到子組件是繼承自Emitter
,Emitter
是一個事件觸發(fā)和監(jiān)聽的類,這個類的實現(xiàn)就是我們今天要學習的。
先來學習tiny-emitter
的實現(xiàn),最后把Emitter
這個類完成。
源碼分析
tiny-emitter
的源碼很簡單,加上注釋和換行也就68
行,先一睹為快:
function E () { // Keep this empty so it's easier to inherit from // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3) } E.prototype = { on: function (name, callback, ctx) { var e = this.e || (this.e = {}); (e[name] || (e[name] = [])).push({ fn: callback, ctx: ctx }); return this; }, once: function (name, callback, ctx) { var self = this; function listener () { self.off(name, listener); callback.apply(ctx, arguments); }; listener._ = callback return this.on(name, listener, ctx); }, emit: function (name) { var data = [].slice.call(arguments, 1); var evtArr = ((this.e || (this.e = {}))[name] || []).slice(); var i = 0; var len = evtArr.length; for (i; i < len; i++) { evtArr[i].fn.apply(evtArr[i].ctx, data); } return this; }, off: function (name, callback) { var e = this.e || (this.e = {}); var evts = e[name]; var liveEvents = []; if (evts && callback) { for (var i = 0, len = evts.length; i < len; i++) { if (evts[i].fn !== callback && evts[i].fn._ !== callback) liveEvents.push(evts[i]); } } // Remove event from queue to prevent memory leak // Suggested by https://github.com/lazd // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910 (liveEvents.length) ? e[name] = liveEvents : delete e[name]; return this; } }; module.exports = E; module.exports.TinyEmitter = E;
一共只有4
個方法,on
、once
、emit
、off
,下面一個一個開始分析。
on
on
方法是用來監(jiān)聽事件的,代碼如下:
function on(name, callback, ctx) { var e = this.e || (this.e = {}); (e[name] || (e[name] = [])).push({ fn: callback, ctx: ctx }); return this; }
它接收三個參數(shù):
name
:事件名稱callback
:事件觸發(fā)時的回調(diào)函數(shù)ctx
:回調(diào)函數(shù)的上下文
它會判斷this.e
是否存在,如果不存在就創(chuàng)建一個空對象;
this.e
是用來存儲事件的,this
指向就不多講了吧。
然后把name
作為key
,callback
和ctx
作為value
,存到this.e
中,用于后面的事件觸發(fā);
最后返回this
,這樣就可以鏈式調(diào)用了。
once
once
方法同樣是用來監(jiān)聽事件的,但是它只會觸發(fā)一次,代碼如下:
function once(name, callback, ctx) { var self = this; function listener() { self.off(name, listener); callback.apply(ctx, arguments); }; listener._ = callback return this.on(name, listener, ctx); }
它同on
方法一樣,接收三個參數(shù),最后也是調(diào)用的on
方法,但是它會在on
方法的基礎(chǔ)上做一些處理。
它內(nèi)部會定義一個listener
函數(shù),然后將這個函數(shù)作為on
方法的回調(diào)函數(shù),最后調(diào)用on
方法。
listener
函數(shù)會調(diào)用off
方法,把自己從事件隊列中移除,然后再調(diào)用callback
函數(shù)。
emit
emit
方法是用來觸發(fā)事件的,代碼如下:
function emit(name) { var data = [].slice.call(arguments, 1); var evtArr = ((this.e || (this.e = {}))[name] || []).slice(); var i = 0; var len = evtArr.length; for (i; i < len; i++) { evtArr[i].fn.apply(evtArr[i].ctx, data); } return this; }
它接收一個參數(shù),就是事件名稱,然后把除了第一個參數(shù)以外的參數(shù),轉(zhuǎn)成數(shù)組,存到data
中。
然后從this.e
中取出name
對應(yīng)的事件隊列,如果沒有就創(chuàng)建一個空數(shù)組,然后把這個數(shù)組復(fù)制一份,存到evtArr
中。
這里老是對this.e
做判斷好麻煩,其實可以在構(gòu)造函數(shù)中初始化this.e
,這樣就不用每次都判斷了。
然后遍歷evtArr
,依次調(diào)用每個事件的回調(diào)函數(shù),把data
作為參數(shù)傳進去,最后返回this
。
off
off
方法是用來移除事件的,代碼如下:
function off(name, callback) { var e = this.e || (this.e = {}); var evts = e[name]; var liveEvents = []; if (evts && callback) { for (var i = 0, len = evts.length; i < len; i++) { if (evts[i].fn !== callback && evts[i].fn._ !== callback) liveEvents.push(evts[i]); } } (liveEvents.length) ? e[name] = liveEvents : delete e[name]; return this; }
它接收兩個參數(shù),name
和callback
,name
是事件名稱,callback
是回調(diào)函數(shù)。
它會從this.e
中取出name
對應(yīng)的事件隊列,然后通過遍歷,把不等于callback
的事件,存到liveEvents
中。
最后判斷liveEvents
是否為空,如果不為空,就把liveEvents
賦值給e[name]
,否則就刪除e[name]
。
動手實現(xiàn)
上面已經(jīng)分析完tiny-emitter
的源碼了,我們現(xiàn)在就來實現(xiàn)最開始提到的Emitter
類。
當然這里還是簡化版,只有on
和emit
方法。
class Emitter { constructor() { this.events = {}; } $on(name, callback) { if (!this.events[name]) { this.events[name] = []; } this.events[name].push(callback); } $emit(name, ...args) { if (this.events[name]) { this.events[name].forEach(callback => callback(...args)); } } }
這里使用ES6
的class
語法來實現(xiàn)這個功能;
constructor
方法用來初始化events
對象,這樣對比tiny-emitter
的源碼,就不用每次都判斷this.e
是否存在了;
$emit
通過...args
來接收除了第一個參數(shù)以外的參數(shù),就可以不用使用arguments
了;
整體來說使用ES6
的class
語法來實現(xiàn),代碼量更少,更加簡潔,來看了結(jié)果:
總結(jié)
通過學習tiny-emitter
的源碼,我們學習到一個事件總線的設(shè)計思路以及實現(xiàn)方式。
了解事件總線的設(shè)計思路,可以幫助我們更好地理解Vue
的事件總線,這可能對我們使用或者閱讀源碼有幫助。
最后通過實現(xiàn)一個簡單的事件總線,讓我們加深對這個事件總線的理解,也通過將這個實現(xiàn)轉(zhuǎn)換為ES6
的class
語法,讓我們對ES6
的class
語法有更深的理解。
以上就是Vue 組件事件觸發(fā)和監(jiān)聽實現(xiàn)源碼解析的詳細內(nèi)容,更多關(guān)于Vue 組件事件觸發(fā)監(jiān)聽的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue中Class和Style實現(xiàn)v-bind綁定的幾種用法
項目開發(fā)中給元素添加/刪除 class 是非常常見的行為之一, 例如網(wǎng)站導(dǎo)航都會給選中項添加一個 active 類用來區(qū)別選與未選中的樣式,那么在 vue 中 我們?nèi)绾翁幚磉@類的效果呢?下面我們就一起來了解一下2021-05-05Vue 中使用vue2-highcharts實現(xiàn)top功能的示例
下面小編就為大家分享一篇Vue 中使用vue2-highcharts實現(xiàn)top功能的示例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-03-03vue .js綁定checkbox并獲取、改變選中狀態(tài)的實例
今天小編就為大家分享一篇vue .js綁定checkbox并獲取、改變選中狀態(tài)的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08vue2.0實現(xiàn)移動端的輸入框?qū)崟r檢索更新列表功能
最近小編在做vue2.0的項目,遇到移動端實時檢索搜索更新列表的效果,下面腳本之家小編給大家?guī)砹藇ue2.0 移動端的輸入框?qū)崟r檢索更新列表功能的實例代碼,感興趣的朋友參考下吧2018-05-05Vue中ElementUI分頁組件Pagination的使用方法
這篇文章主要為大家詳細介紹了Vue中ElementUI分頁組件Pagination的使用,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-05-05詳解vue項目打包后通過百度的BAE發(fā)布到網(wǎng)上的流程
這篇文章主要介紹了將vue的項目打包后通過百度的BAE發(fā)布到網(wǎng)上的流程,主要運用的技術(shù)是vue+express+git+百度的應(yīng)用引擎BAE。需要的朋友可以參考下2018-03-03深入探討Vue計算屬性與監(jiān)聽器的區(qū)別和用途
在Vue的開發(fā)中,計算屬性(Computed Properties)和監(jiān)聽器(Watchers)是兩種非常重要的概念,它們都用于響應(yīng)式地處理數(shù)據(jù)變化,本文將帶你深入了解計算屬性和監(jiān)聽器的區(qū)別,以及在何時使用它們,感興趣的朋友可以參考下2023-09-09