衆所周知,EventEmitter是咱們前端開發常常須要使用的一個類(node端自帶,瀏覽器端gaythub上有許多現成的npm包)。它可以幫助咱們訂閱、發送自定義事件,在跨組件通訊中常常用到。但使用這個類的時候有個問題:咱們必須在發射某事件前訂閱它,後來訂閱者是沒法接收前面的事件的。前端
項目中每每一些千奇百怪的bug在定位後發現,就是由於錯誤這個時機而形成的:( 所以咱們須要額外當心這個訂閱時機。node
爲了解決這個說大不大,說小不小的問題,今天咱們就來造個輪子,實現一個帶歷史消息的EventEmiitter吧typescript
首先咱們先實現一個普通的EventEmitter,它應該包含on
、off
、emit
三個基本的API,此外,爲了更好地說明一些參數格式,我這裏使用了typescript,不瞭解ts的童鞋也不要緊,它的格式很是簡單易懂~npm
class EventEmitter {
private eventStack: Array<{
name: string;
callback: Function;
}>;
constructor() {
this.eventStack = [];
}
// 訂閱事件
on(name: string, callback: Function) {
this.eventStack.push({ name, callback });
}
// 取消訂閱
off(name?: string, callback?: Function) {
if (!name) {
this.eventStack = [];
} else if (!callback) {
this.eventStack.filter(item => item.name !== name);
} else {
this.eventStack.filter(
item => item.name !== name && item.callback !== callback,
);
}
}
// 發射事件
emit(name: string, data?: any) {
data = data || null;
this.eventStack.forEach(item => {
item.name === name && item.callback(data);
});
}
}
複製代碼
首先咱們定義了一個EventEmitter
類,同時也定義了on
、off
、emit
三個基本API,同時定義了一個內部屬性eventStack
—— 一個存放訂閱對象的數組(訂閱對象是一個由事件名name
和回調函數callback
組成的對象),它是實現該類的核心。數組
當調用者執行on
方法時,其實就是將事件名和回調函數組成訂閱者對象,而後推入eventStack
瀏覽器
因爲off
支持取消訂閱某一事件的某一回調、取消訂閱某一事件的全部回調,以及取消全部事件的訂閱三種狀況,所以這裏根據傳參的有無,利用filter
刪除了eventStack
的部分訂閱對象函數
這個方法也很簡單,利用forEach
找到一致事件名的訂閱對象,執行其回調便可「發射出去」ui
接下來是重點內容,爲了讓後來訂閱者能接收先前的消息,咱們須要增長一個叫「歷史消息」的東西,它應該存放EventEmitter
從建立之初到迄今的全部事件和數據,所以咱們新增了一個內部屬性:this
// 歷史消息,name:事件名,data:消息數據
private historyData: Array<{
name: string;
data: any;
}>;
複製代碼
而後咱們在emit
方法裏作一些更改,在發射當前消息之餘,咱們也應該將其保存到historyData
中:spa
emit(name: string, data?: any) {
data = data || null;
// 保存事件歷史
this.historyData.push({ name, data });
// 發射事件
this.eventStack.forEach(item => {
item.name === name && item.callback(data);
});
}
複製代碼
其次,在on
方法中咱們也須要進行相應修改:在訂閱的一剎那查閱下歷史消息,康康是否存在同類型的事件,若是有,則直接執行回調,此外,爲了提升on
方法的擴展性,咱們增長了第三個參數,用於決定是否開啓「歷史消息推送」功能:
on(name: string, callback: Function, isNeedHistoryData?: boolean) {
this.eventStack.push({ name, callback });
isNeedHistoryData &&
this.historyData.forEach(item => {
item.name === name && callback(item.data);
});
}
複製代碼
finally,咱們的最終代碼長這樣:
export default class Event {
private historyData: Array<{
name: string;
data: any;
}>;
private eventStack: Array<{
name: string;
callback: Function;
}>;
constructor() {
this.historyData = [];
this.eventStack = [];
}
on(name: string, callback: Function, isNeedHistoryData?: boolean) {
this.eventStack.push({ name, callback });
isNeedHistoryData &&
this.historyData.forEach(item => {
item.name === name && callback(item.data);
});
}
off(name?: string, callback?: Function) {
if (!name) {
this.eventStack = [];
} else if (!callback) {
this.eventStack.filter(item => item.name !== name);
} else {
this.eventStack.filter(
item => item.name !== name && item.callback !== callback,
);
}
}
emit(name: string, data?: any) {
data = data || null;
// 保存事件歷史
this.historyData.push({ name, data });
// 發射事件
this.eventStack.forEach(item => {
item.name === name && item.callback(data);
});
}
}
複製代碼
以上代碼拷貝後就能夠直接運用到項目中了,是否是很簡單?
今後之後,媽媽不再用擔憂我錯過訂閱事件的時機啦n(≧▽≦)n