本文標題的題目是由其餘問題延伸而來,面試中面試官的經常使用套路,揪住一個問題一直深挖,在產生這個問題以前必定是這個問題.html
React/Vue不一樣組件之間是怎麼通訊的?node
Vuegit
父子組件用Props通訊es6
非父子組件用Event Bus通訊github
若是項目夠複雜,可能須要Vuex等全局狀態管理庫通訊面試
$dispatch
(已經廢除)和$broadcast
(已經廢除)api
React數組
父子組件,父->子直接用Props,子->父用callback回調app
非父子組件,用發佈訂閱模式的Event模塊ide
項目複雜的話用Redux、Mobx等全局狀態管理管庫
用新的Context Api
咱們大致上都會有以上回答,接下來極可能會問到如何實現Event(Bus)
,由於這個東西過重要了,幾乎全部的模塊通訊都是基於相似的模式,包括安卓開發中的Event Bus
,Node.js中的Event
模塊(Node中幾乎全部的模塊都依賴於Event,包括不限於http、stream、buffer、fs
等).
咱們仿照Node中Event API實現一個簡單的Event庫,他是發佈訂閱模式的典型應用.
提早聲明: 咱們沒有對傳入的參數進行及時判斷而規避錯誤,僅僅對核心方法進行了實現.
1.1初始化class
咱們利用ES6的class
關鍵字對Event
進行初始化,包括Event
的事件清單和監聽者上限.
咱們選擇了Map
做爲儲存事件的結構,由於做爲鍵值對的儲存方式Map
比通常對象更加適合,咱們操做起來也更加簡潔,能夠先看一下Map的基本用法與特色.
class EventEmeitter {
constructor() {
this._events = this._events || new Map(); // 儲存事件/回調鍵值對
this._maxListeners = this._maxListeners || 10; // 設立監聽上限
}
}
1.2 監聽與觸發
觸發監聽函數咱們能夠用apply
與call
兩種方法,在少數參數時call
的性能更好,多個參數時apply
性能更好,當年Node的Event模塊就在三個參數如下用call
不然用apply
.
固然當Node全面擁抱ES6+以後,相應的call/apply
操做用Reflect
新關鍵字重寫了,可是咱們不想寫的那麼複雜,就作了一個簡化版.
// 觸發名爲type的事件
EventEmeitter.prototype.emit = function(type, ...args){
let handler; // 從儲存事件鍵值對的this._events中獲取對應事件回調函數
handler = this._events.get(type);
if (args.length > 0) {
handler.apply(this, args);
}else{
handler.call(this);
}
return true;
};
// 監聽名爲type的事件
EventEmeitter.prototype.addListener = function(type, fn) {
// 將type事件以及對應的fn函數放入this._events中儲存
if (!this._events.get(type)) {
this._events.set(type, fn);
}
};
咱們實現了觸發事件的emit
方法和監聽事件的addListener
方法,至此咱們就能夠進行簡單的實踐了.
// 實例化 const emitter = new EventEmeitter();
// 監聽一個名爲arson的事件對應一個回調函數
emitter.addListener('arson', man => {
console.log(`expel ${man}`);
});
// 咱們觸發arson事件,發現回調成功執行
emitter.emit('arson', 'low-end'); // expel low-end
彷佛不錯,咱們實現了基本的觸發/監聽,可是若是有多個監聽者呢?
// 重複監聽同一個事件名
emitter.addListener('arson', man => {
console.log(`expel ${man}`);
});
emitter.addListener('arson', man => {
console.log(`save ${man}`);
});
emitter.emit('arson', 'low-end'); // expel low-end
是的,只會觸發第一個,所以咱們須要進行改造.
2.1 監聽/觸發器升級
咱們的addListener
實現方法還不夠健全,在綁定第一個監聽者以後,咱們就沒法對後續監聽者進行綁定了,所以咱們須要將後續監聽者與第一個監聽者函數放到一個數組裏.
// 觸發名爲type的事件 EventEmeitter.prototype.emit = function(type, ...args) { let handler; handler = this._events.get(type); if (Array.isArray(handler)) { // 若是是一個數組說明有多個監聽者,須要依次此觸發裏面的函數 for (let i = 0; i < handler.length; i++) { if (args.length > 0) { handler[i].apply(this, args); } else { handler[i].call(this); } } } else { // 單個函數的狀況咱們直接觸發便可 if (args.length > 0) { handler.apply(this, args); } else { handler.call(this); } } return true; }; // 監聽名爲type的事件 EventEmeitter.prototype.addListener = function(type, fn) { const handler = this._events.get(type); // 獲取對應事件名稱的函數清單 if (!handler) { this._events.set(type, fn); } else if (handler && typeof handler === 'function') { // 若是handler是函數說明只有一個監聽者 this._events.set(type, [handler, fn]); // 多個監聽者咱們須要用數組儲存 } else { handler.push(fn); // 已經有多個監聽者,那麼直接往數組裏push函數便可 } };
是的,今後之後能夠愉快的觸發多個監聽者的函數了.
// 監聽同一個事件名 emitter.addListener('arson', man => { console.log(`expel ${man}`); }); emitter.addListener('arson', man => { console.log(`save ${man}`); }); emitter.addListener('arson', man => { console.log(`kill ${man}`); }); // 觸發事件 emitter.emit('arson', 'low-end'); //expel low-end //save low-end //kill low-end
2.2 移除監聽
咱們會用removeListener
函數移除監聽函數,可是匿名函數是沒法移除的.
EventEmeitter.prototype.removeListener = function(type, fn) { const handler = this._events.get(type); // 獲取對應事件名稱的函數清單 // 若是是函數,說明只被監聽了一次 if (handler && typeof handler === 'function') { this._events.delete(type, fn); }else{ let postion; // 若是handler是數組,說明被監聽屢次要找到對應的函數 for (let i = 0; i < handler.length; i++) { if (handler[i] === fn) { postion = i; }else{ postion = -1; } } // 若是找到匹配的函數,從數組中清除 if(postion !== -1){ // 找到數組對應的位置,直接清除此回調 handler.splice(postion, 1); // 若是清除後只有一個函數,那麼取消數組,以函數形式保存 if (handler.length === 1){ this._events.set(type, handler[0]); } }else{ return this; } } };
咱們已經基本完成了Event
最重要的幾個方法,也完成了升級改造,能夠說一個Event
的骨架是被咱們開發出來了,可是它仍然有不足和須要補充的地方.
魯棒性不足: 咱們沒有對參數進行充分的判斷,沒有完善的報錯機制.
模擬不夠充分: 除了
removeAllListeners
這些方法沒有實現之外,例如監聽時間後會觸發newListener
事件,咱們也沒有實現,另外最開始的監聽者上限咱們也沒有利用到.
固然,這在面試中現場寫一個Event已是很夠意思了,主要是體現出來對發佈-訂閱模式的理解,以及針對多個監聽情況下的處理,不可能現場擼幾百行寫一個完整Event.
索性Event庫幫咱們實現了完整的特性,整個代碼量有300多行,很適合閱讀,你能夠花十分鐘的時間通讀一下,見識一下完整的Event實現.
做者:尋找海藍96連接:https://juejin.im/post/5ac2fb886fb9a028b86e328c