本身動手,實現一個帶歷史消息的EventEmitter

前言

衆所周知,EventEmitter是咱們前端開發常常須要使用的一個類(node端自帶,瀏覽器端gaythub上有許多現成的npm包)。它可以幫助咱們訂閱、發送自定義事件,在跨組件通訊中常常用到。但使用這個類的時候有個問題:咱們必須在發射某事件前訂閱它,後來訂閱者是沒法接收前面的事件的。前端

項目中每每一些千奇百怪的bug在定位後發現,就是由於錯誤這個時機而形成的:( 所以咱們須要額外當心這個訂閱時機。node

爲了解決這個說大不大,說小不小的問題,今天咱們就來造個輪子,實現一個帶歷史消息的EventEmiittertypescript

實現一個常規的EventEmitter

首先咱們先實現一個普通的EventEmitter,它應該包含onoffemit三個基本的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類,同時也定義了onoffemit三個基本API,同時定義了一個內部屬性eventStack —— 一個存放訂閱對象的數組(訂閱對象是一個由事件名name和回調函數callback組成的對象),它是實現該類的核心。數組

on

當調用者執行on方法時,其實就是將事件名和回調函數組成訂閱者對象,而後推入eventStack瀏覽器

off

因爲off支持取消訂閱某一事件的某一回調、取消訂閱某一事件的全部回調,以及取消全部事件的訂閱三種狀況,所以這裏根據傳參的有無,利用filter刪除了eventStack的部分訂閱對象函數

emit

這個方法也很簡單,利用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

相關文章
相關標籤/搜索