EventBus是消息傳遞的一種方式,基於一個消息中心,訂閱和發布消息的模式。Vue中就有一個消息總線的機制。當然消息總線不僅僅局限於WEB前端,Android 、IOS中的消息中心也是如此實現的。
[1] 設計模式:訂閱者發布者模式。
[2] API的設計:
1. 只能構造一個消息對象。
2. on('msgName', func) 訂閱消息,msgName:訂閱的消息名稱 ;func: 訂閱的消息,會在執行$emit 後調用。
3. once('msgName', func) 僅訂閱一次消息,後訂閱的會替換前面訂閱的消息。
4. emit('msgName', msg) 發布消息 msgName:消息名稱;msg:可選 發布的消息,傳遞給監聽函數的參數。
5. off('msgName') 移除消息。
Vue EventBus 源碼解析VUE中EventBus可以用來進行任何組件之間的通信。EventBus可以當成一個管道,這個管道兩端可以接好多組件,兩端的任何一個組件都可以進行通信。這個管道就是Vue實例,實例中有四個跟事件派發相關的方法$on $off $emit $once。API的設計跟上面提到的一樣,不過它做了一些增強。
首先總體上介紹一下Vue EventBus的原理:
[1] Vue在實例化的時候會進行很多初始化的操作,其中包括 eventMixin 方法,該方法裡包含了上面提及的四個方法。
[2] 四個方法的實現原理如下:
vue實例上會創建一個對象來保存所有要監聽的事件:vm._events = {}
每當我們要監聽一個事件,就往vm.events裡添加一個鍵值對,事件的名稱作為鍵,一個空數組作為值。例如我們要監聽的事件名稱為event1,則vm._events = {event1: []}
監聽事件的回調函數都會添加到對應的數組中,例如我們調用
vm.$on('event1', cb1);
vm.$on('event1', cb2);
vm.$on('event1', cb3);則此時vm._events={event1: [cb1, cb2, cb3]}
當調用移除監聽事件的方法時,所做的操作為移除其對應數組裡的回調函數,例如當我們調用
vm.$off('event1', cb1);這時cb1就被移除了,vm._events={event1: [cb2, cb3]}
當我們執行$emit觸發對應事件時,所做的操作就是把該事件對應數組裡的回調函數都拿出來執行一遍,例如當我們調用
vm.$emit('event1')這時候會取出event1對應數組裡的cb2和cb3執行
$once表示該事件只會觸發執行一次,後面在觸發就沒用了,例如當我們調用:
// 用$on方法監聽event2,回調函數為cb4
vm.$on('event2', cb4);
// 用$once方法監聽event2,回調函數為cb5
vm.$once('event2', cb5);
// 觸發event2事件,會執行cb4和cb5
vm.$emit('event2');
// 再次觸發event2事件,這裡只會執行cb4,不會執行cb5,cb5隻會執行一次
vm.$emit('event2');
以上即為事件派發方法的基本原理,當然這些方法還有一些稍微複雜一點的使用方式,比如
$on(['event1', 'event2'], cb) // 監聽多個事件
$off(['event1', 'event2'], cb) // 移除多個事件
$off() // 移除事件不傳參數
$off('event1') // 移除事件傳一個參數
$off('event1', cb) // 移除事件傳兩個參數
$emit('event1', param1, param2) // 觸發事件傳參數
看完下面的源碼解析就知道這些情況都是怎麼處理的了。
function eventsMixin(Vue) { Vue.prototype.$on = function(event, fn) {} Vue.prototype.$once = function(event, fn) {} Vue.prototype.$off = function(event, fn) {} Vue.prototype.$emit = function(event, fn) {}}Vue.prototype.$on = function(event, fn) { const vm = this if (Array.isArray(event)) { for (let i = 0, l = event.length; i < l; i++) { vm.$on(event[i], fn); } } else { (vm._events[event] || (vm._events[event] = [])).push(fn); } return vm}Vue.prototype.$emit = function(event) { const vm = this; const cbs = vm._events[event]; if (cbs) { 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;}Vue.prototype.$once = function(event, fn) { const vm = this function on() { vm.$off(event, on); fn.apply(vm, arguments); } on.fn = fn; vm.$on(event, on); return vm;}Vue.prototype.$off = function(event, fn) { const vm = this; if (!arguments.length) { vm._events = Object.create(null); return vm; } if (Array.isArray(event)) { for (let i = 0, l = event.length; i < l; i++) { vm.$off(event[i], fn); } return vm; } const cbs = vm._events[event] if (!cbs) { return vm; } if (!fn) { vm._events[event] = null return vm; } let cb; let i = cbs.length; while (i--) { cb = cbs[i]; if (cb === fn || cb.fn === fn) { cbs.splice(i, 1); break; } } return vm;}自己實現一個EventBus
按照最初的API設計和參考Vue EventBus創建一個自己的EventBus。這個沒有支持批量的發布和訂閱事件。
class MyEventBus { constructor() { this.msgQueue = {}; } on(event, fn) { if (this.msgQueue.hasOwnProperty(event)) { if (this.msgQueue[event] === 'function') { this.msgQueue[event] = [this.msgQueue[event], fn]; } else { this.msgQueue[event] = [...this.msgQueue[event], fn]; } } else { this.msgQueue[event] = fn; } } once(event, fn) { this.msgQueue[event] = fn; } emit(event, ...msg) { if (!this.msgQueue.hasOwnProperty(event)) return; try { if (typeof this.msgQueue[event] === 'function') { this.msgQueue[event](...msg); } else { this.msgQueue[event].map(fn = >{ fn(...msg) }) } } catch(e) { console.log(e); } } off(event) { if (!this.msgQueue.hasOwnProperty(event)) return; delete this.msgQueue[event] }}
const EventBus = new MyEventBus();EventBus.on('event1', (...msg) = >{ console.log('訂閱的消息是', msg)});EventBus.emit('event1', 'hello world', '123');EventBus.off('event1');EventBus.emit('event1', 'hello2world');小結本次分享首先介紹了EventBus原理和API設計,然後介紹了VUE中EventBus實現方式,最後自己實現了一個EventBus。