如何在 JavaScript 中實(shí)現(xiàn)事件總線 (Event Bus)

原文:https://lwebapp.com/zh/post/event-bus

介紹

Event Bus 事件總線竣付,通常作為多個模塊間的通信機(jī)制诡延,相當(dāng)于一個事件管理中心,一個模塊發(fā)送消息古胆,其它模塊接受消息肆良,就達(dá)到了通信的作用。

比如逸绎,Vue 組件間的數(shù)據(jù)傳遞可以使用一個 Event Bus 來通信惹恃,也可以用作微內(nèi)核插件系統(tǒng)中的插件和核心通信。

原理

Event Bus 本質(zhì)上是采用了發(fā)布-訂閱的設(shè)計模式棺牧,比如多個模塊 A巫糙、BC 訂閱了一個事件 EventX颊乘,然后某一個模塊 X 在事件總線發(fā)布了這個事件参淹,那么事件總線會負(fù)責(zé)通知所有訂閱者 A醉锄、BC浙值,它們都能收到這個通知消息恳不,同時還可以傳遞參數(shù)。

// 關(guān)系圖
                           模塊X
                            ?發(fā)布EventX
╔════════════════════════════════════════════════════════════════════╗
║                         Event Bus                                  ║
║                                                                    ║
║         【EventX】       【EventY】       【EventZ】   ...           ║
╚════════════════════════════════════════════════════════════════════╝
  ?訂閱EventX            ?訂閱EventX           ?訂閱EventX
 模塊A                   模塊B                  模塊C

分析

如何使用 JavaScript 來實(shí)現(xiàn)一個簡單版本的 Event Bus

  • 首先構(gòu)造一個 EventBus 類开呐,初始化一個空對象用于存放所有的事件
  • 在接受訂閱時烟勋,將事件名稱作為 key 值,將需要在接受發(fā)布消息后執(zhí)行的回調(diào)函數(shù)作為 value 值筐付,由于一個事件可能有多個訂閱者神妹,所以這里的回調(diào)函數(shù)要存儲成列表
  • 在發(fā)布事件消息時,從事件列表里取得指定的事件名稱對應(yīng)的所有回調(diào)函數(shù)家妆,依次觸發(fā)執(zhí)行即可

以下是代碼詳細(xì)實(shí)現(xiàn)鸵荠,可以復(fù)制到谷歌瀏覽器控制臺直接運(yùn)行檢測效果。

代碼

class EventBus {
  constructor() {
    // 初始化事件列表
    this.eventObject = {};
  }
  // 發(fā)布事件
  publish(eventName) {
    // 取出當(dāng)前事件所有的回調(diào)函數(shù)
    const callbackList = this.eventObject[eventName];

    if (!callbackList) return console.warn(eventName + " not found!");

    // 執(zhí)行每一個回調(diào)函數(shù)
    for (let callback of callbackList) {
      callback();
    }
  }
  // 訂閱事件
  subscribe(eventName, callback) {
    // 初始化這個事件
    if (!this.eventObject[eventName]) {
      this.eventObject[eventName] = [];
    }

    // 存儲訂閱者的回調(diào)函數(shù)
    this.eventObject[eventName].push(callback);
  }
}

// 測試
const eventBus = new EventBus();

// 訂閱事件eventX
eventBus.subscribe("eventX", () => {
  console.log("模塊A");
});
eventBus.subscribe("eventX", () => {
  console.log("模塊B");
});
eventBus.subscribe("eventX", () => {
  console.log("模塊C");
});

// 發(fā)布事件eventX
eventBus.publish("eventX");

// 輸出
> 模塊A
> 模塊B
> 模塊C

上面我們實(shí)現(xiàn)了最基礎(chǔ)的發(fā)布和訂閱功能伤极,實(shí)際應(yīng)用中蛹找,還可能有更進(jìn)階的需求。

進(jìn)階

1. 如何在發(fā)送消息時傳遞參數(shù)

發(fā)布者傳入一個參數(shù)到 EventBus 中哨坪,在 callback 回調(diào)函數(shù)執(zhí)行的時候接著傳出參數(shù)庸疾,這樣每一個訂閱者就可以收到參數(shù)了。

代碼

class EventBus {
  constructor() {
    // 初始化事件列表
    this.eventObject = {};
  }
  // 發(fā)布事件
  publish(eventName, ...args) {
    // 取出當(dāng)前事件所有的回調(diào)函數(shù)
    const callbackList = this.eventObject[eventName];

    if (!callbackList) return console.warn(eventName + " not found!");

    // 執(zhí)行每一個回調(diào)函數(shù)
    for (let callback of callbackList) {
      // 執(zhí)行時傳入?yún)?shù)
      callback(...args);
    }
  }
  // 訂閱事件
  subscribe(eventName, callback) {
    // 初始化這個事件
    if (!this.eventObject[eventName]) {
      this.eventObject[eventName] = [];
    }

    // 存儲訂閱者的回調(diào)函數(shù)
    this.eventObject[eventName].push(callback);
  }
}

// 測試
const eventBus = new EventBus();

// 訂閱事件eventX
eventBus.subscribe("eventX", (obj, num) => {
  console.log("模塊A", obj, num);
});
eventBus.subscribe("eventX", (obj, num) => {
  console.log("模塊B", obj, num);
});
eventBus.subscribe("eventX", (obj, num) => {
  console.log("模塊C", obj, num);
});

// 發(fā)布事件eventX
eventBus.publish("eventX", { msg: "EventX published!" }, 1);


// 輸出
> 模塊A {msg: 'EventX published!'} 1
> 模塊B {msg: 'EventX published!'} 1
> 模塊C {msg: 'EventX published!'} 1

2. 訂閱后如何取消訂閱

有時候訂閱者只想在某一個時間段訂閱消息当编,這就涉及帶取消訂閱功能届慈。我們將對代碼進(jìn)行改造。

首先忿偷,要實(shí)現(xiàn)指定訂閱者取消訂閱金顿,每一次訂閱事件時,都生成唯一一個取消訂閱的函數(shù)鲤桥,用戶直接調(diào)用這個函數(shù)揍拆,我們就把當(dāng)前訂閱的回調(diào)函數(shù)刪除。

// 每一次訂閱事件茶凳,都生成唯一一個取消訂閱的函數(shù)
const unSubscribe = () => {
  // 清除這個訂閱者的回調(diào)函數(shù)
  delete this.eventObject[eventName][id];
};

其次嫂拴,訂閱的回調(diào)函數(shù)列表使換成對象結(jié)構(gòu)存儲,為每一個回調(diào)函數(shù)設(shè)定一個唯一 id贮喧, 注銷回調(diào)函數(shù)的時候可以提高刪除的效率筒狠,如果還是使用數(shù)組的話需要使用 split 刪除,效率不如對象的 delete箱沦。

代碼

class EventBus {
  constructor() {
    // 初始化事件列表
    this.eventObject = {};
    // 回調(diào)函數(shù)列表的id
    this.callbackId = 0;
  }
  // 發(fā)布事件
  publish(eventName, ...args) {
    // 取出當(dāng)前事件所有的回調(diào)函數(shù)
    const callbackObject = this.eventObject[eventName];

    if (!callbackObject) return console.warn(eventName + " not found!");

    // 執(zhí)行每一個回調(diào)函數(shù)
    for (let id in callbackObject) {
      // 執(zhí)行時傳入?yún)?shù)
      callbackObject[id](...args);
    }
  }
  // 訂閱事件
  subscribe(eventName, callback) {
    // 初始化這個事件
    if (!this.eventObject[eventName]) {
      // 使用對象存儲辩恼,注銷回調(diào)函數(shù)的時候提高刪除的效率
      this.eventObject[eventName] = {};
    }

    const id = this.callbackId++;

    // 存儲訂閱者的回調(diào)函數(shù)
    // callbackId使用后需要自增,供下一個回調(diào)函數(shù)使用
    this.eventObject[eventName][id] = callback;

    // 每一次訂閱事件,都生成唯一一個取消訂閱的函數(shù)
    const unSubscribe = () => {
      // 清除這個訂閱者的回調(diào)函數(shù)
      delete this.eventObject[eventName][id];

      // 如果這個事件沒有訂閱者了运挫,也把整個事件對象清除
      if (Object.keys(this.eventObject[eventName]).length === 0) {
        delete this.eventObject[eventName];
      }
    };

    return { unSubscribe };
  }
}

// 測試
const eventBus = new EventBus();

// 訂閱事件eventX
eventBus.subscribe("eventX", (obj, num) => {
  console.log("模塊A", obj, num);
});
eventBus.subscribe("eventX", (obj, num) => {
  console.log("模塊B", obj, num);
});
const subscriberC = eventBus.subscribe("eventX", (obj, num) => {
  console.log("模塊C", obj, num);
});

// 發(fā)布事件eventX
eventBus.publish("eventX", { msg: "EventX published!" }, 1);

// 模塊C取消訂閱
subscriberC.unSubscribe();

// 再次發(fā)布事件eventX,模塊C不會再收到消息了
eventBus.publish("eventX", { msg: "EventX published again!" }, 2);

// 輸出
> 模塊A {msg: 'EventX published!'} 1
> 模塊B {msg: 'EventX published!'} 1
> 模塊C {msg: 'EventX published!'} 1
> 模塊A {msg: 'EventX published again!'} 2
> 模塊B {msg: 'EventX published again!'} 2

3. 如何只訂閱一次

如果一個事件只發(fā)生一次套耕,通常也只需要訂閱一次谁帕,收到消息后就不用再接受消息。

首先冯袍,我們提供一個 subscribeOnce 的接口匈挖,內(nèi)部實(shí)現(xiàn)幾乎和 subscribe 一樣,只有一個地方有區(qū)別康愤,在 callbackId 前面的加一個字符 d儡循,用來標(biāo)示這是一個需要刪除的訂閱。

// 標(biāo)示為只訂閱一次的回調(diào)函數(shù)
const id = "d" + this.callbackId++;

然后征冷,在執(zhí)行回調(diào)函數(shù)后判斷當(dāng)前回調(diào)函數(shù)的 id 有沒有標(biāo)示择膝,決定我們是否需要刪除這個回調(diào)函數(shù)。

// 只訂閱一次的回調(diào)函數(shù)需要刪除
if (id[0] === "d") {
  delete callbackObject[id];
}

代碼

class EventBus {
  constructor() {
    // 初始化事件列表
    this.eventObject = {};
    // 回調(diào)函數(shù)列表的id
    this.callbackId = 0;
  }
  // 發(fā)布事件
  publish(eventName, ...args) {
    // 取出當(dāng)前事件所有的回調(diào)函數(shù)
    const callbackObject = this.eventObject[eventName];

    if (!callbackObject) return console.warn(eventName + " not found!");

    // 執(zhí)行每一個回調(diào)函數(shù)
    for (let id in callbackObject) {
      // 執(zhí)行時傳入?yún)?shù)
      callbackObject[id](...args);

      // 只訂閱一次的回調(diào)函數(shù)需要刪除
      if (id[0] === "d") {
        delete callbackObject[id];
      }
    }
  }
  // 訂閱事件
  subscribe(eventName, callback) {
    // 初始化這個事件
    if (!this.eventObject[eventName]) {
      // 使用對象存儲检激,注銷回調(diào)函數(shù)的時候提高刪除的效率
      this.eventObject[eventName] = {};
    }

    const id = this.callbackId++;

    // 存儲訂閱者的回調(diào)函數(shù)
    // callbackId使用后需要自增肴捉,供下一個回調(diào)函數(shù)使用
    this.eventObject[eventName][id] = callback;

    // 每一次訂閱事件,都生成唯一一個取消訂閱的函數(shù)
    const unSubscribe = () => {
      // 清除這個訂閱者的回調(diào)函數(shù)
      delete this.eventObject[eventName][id];

      // 如果這個事件沒有訂閱者了叔收,也把整個事件對象清除
      if (Object.keys(this.eventObject[eventName]).length === 0) {
        delete this.eventObject[eventName];
      }
    };

    return { unSubscribe };
  }

  // 只訂閱一次
  subscribeOnce(eventName, callback) {
    // 初始化這個事件
    if (!this.eventObject[eventName]) {
      // 使用對象存儲齿穗,注銷回調(diào)函數(shù)的時候提高刪除的效率
      this.eventObject[eventName] = {};
    }

    // 標(biāo)示為只訂閱一次的回調(diào)函數(shù)
    const id = "d" + this.callbackId++;

    // 存儲訂閱者的回調(diào)函數(shù)
    // callbackId使用后需要自增,供下一個回調(diào)函數(shù)使用
    this.eventObject[eventName][id] = callback;

    // 每一次訂閱事件饺律,都生成唯一一個取消訂閱的函數(shù)
    const unSubscribe = () => {
      // 清除這個訂閱者的回調(diào)函數(shù)
      delete this.eventObject[eventName][id];

      // 如果這個事件沒有訂閱者了窃页,也把整個事件對象清除
      if (Object.keys(this.eventObject[eventName]).length === 0) {
        delete this.eventObject[eventName];
      }
    };

    return { unSubscribe };
  }
}

// 測試
const eventBus = new EventBus();

// 訂閱事件eventX
eventBus.subscribe("eventX", (obj, num) => {
  console.log("模塊A", obj, num);
});
eventBus.subscribeOnce("eventX", (obj, num) => {
  console.log("模塊B", obj, num);
});
eventBus.subscribe("eventX", (obj, num) => {
  console.log("模塊C", obj, num);
});

// 發(fā)布事件eventX
eventBus.publish("eventX", { msg: "EventX published!" }, 1);

// 再次發(fā)布事件eventX,模塊B只訂閱了一次复濒,不會再收到消息了
eventBus.publish("eventX", { msg: "EventX published again!" }, 2);

// 輸出
> 模塊A {msg: 'EventX published!'} 1
> 模塊C {msg: 'EventX published!'} 1
> 模塊B {msg: 'EventX published!'} 1
> 模塊A {msg: 'EventX published again!'} 2
> 模塊C {msg: 'EventX published again!'} 2

4. 如何清除某個事件或者所有事件

我們還希望通過一個 clear 的操作來將指定事件的所有訂閱清除掉脖卖,這個通常在一些組件或者模塊卸載的時候用到。

  // 清除事件
  clear(eventName) {
    // 未提供事件名稱巧颈,默認(rèn)清除所有事件
    if (!eventName) {
      this.eventObject = {};
      return;
    }

    // 清除指定事件
    delete this.eventObject[eventName];
  }

和取消訂閱的邏輯相似胚嘲,只不過這里統(tǒng)一處理了。

代碼

class EventBus {
  constructor() {
    // 初始化事件列表
    this.eventObject = {};
    // 回調(diào)函數(shù)列表的id
    this.callbackId = 0;
  }
  // 發(fā)布事件
  publish(eventName, ...args) {
    // 取出當(dāng)前事件所有的回調(diào)函數(shù)
    const callbackObject = this.eventObject[eventName];

    if (!callbackObject) return console.warn(eventName + " not found!");

    // 執(zhí)行每一個回調(diào)函數(shù)
    for (let id in callbackObject) {
      // 執(zhí)行時傳入?yún)?shù)
      callbackObject[id](...args);

      // 只訂閱一次的回調(diào)函數(shù)需要刪除
      if (id[0] === "d") {
        delete callbackObject[id];
      }
    }
  }
  // 訂閱事件
  subscribe(eventName, callback) {
    // 初始化這個事件
    if (!this.eventObject[eventName]) {
      // 使用對象存儲洛二,注銷回調(diào)函數(shù)的時候提高刪除的效率
      this.eventObject[eventName] = {};
    }

    const id = this.callbackId++;

    // 存儲訂閱者的回調(diào)函數(shù)
    // callbackId使用后需要自增馋劈,供下一個回調(diào)函數(shù)使用
    this.eventObject[eventName][id] = callback;

    // 每一次訂閱事件,都生成唯一一個取消訂閱的函數(shù)
    const unSubscribe = () => {
      // 清除這個訂閱者的回調(diào)函數(shù)
      delete this.eventObject[eventName][id];

      // 如果這個事件沒有訂閱者了晾嘶,也把整個事件對象清除
      if (Object.keys(this.eventObject[eventName]).length === 0) {
        delete this.eventObject[eventName];
      }
    };

    return { unSubscribe };
  }

  // 只訂閱一次
  subscribeOnce(eventName, callback) {
    // 初始化這個事件
    if (!this.eventObject[eventName]) {
      // 使用對象存儲妓雾,注銷回調(diào)函數(shù)的時候提高刪除的效率
      this.eventObject[eventName] = {};
    }

    // 標(biāo)示為只訂閱一次的回調(diào)函數(shù)
    const id = "d" + this.callbackId++;

    // 存儲訂閱者的回調(diào)函數(shù)
    // callbackId使用后需要自增,供下一個回調(diào)函數(shù)使用
    this.eventObject[eventName][id] = callback;

    // 每一次訂閱事件垒迂,都生成唯一一個取消訂閱的函數(shù)
    const unSubscribe = () => {
      // 清除這個訂閱者的回調(diào)函數(shù)
      delete this.eventObject[eventName][id];

      // 如果這個事件沒有訂閱者了械姻,也把整個事件對象清除
      if (Object.keys(this.eventObject[eventName]).length === 0) {
        delete this.eventObject[eventName];
      }
    };

    return { unSubscribe };
  }

  // 清除事件
  clear(eventName) {
    // 未提供事件名稱,默認(rèn)清除所有事件
    if (!eventName) {
      this.eventObject = {};
      return;
    }

    // 清除指定事件
    delete this.eventObject[eventName];
  }
}

// 測試
const eventBus = new EventBus();

// 訂閱事件eventX
eventBus.subscribe("eventX", (obj, num) => {
  console.log("模塊A", obj, num);
});
eventBus.subscribe("eventX", (obj, num) => {
  console.log("模塊B", obj, num);
});
eventBus.subscribe("eventX", (obj, num) => {
  console.log("模塊C", obj, num);
});

// 發(fā)布事件eventX
eventBus.publish("eventX", { msg: "EventX published!" }, 1);

// 清除
eventBus.clear("eventX");

// 再次發(fā)布事件eventX机断,由于已經(jīng)清除楷拳,所有模塊都不會再收到消息了
eventBus.publish("eventX", { msg: "EventX published again!" }, 2);

// 輸出
> 模塊A {msg: 'EventX published!'} 1
> 模塊B {msg: 'EventX published!'} 1
> 模塊C {msg: 'EventX published!'} 1
> eventX not found!

5. TypeScript 版本

鑒于現(xiàn)在 TypeScript 已經(jīng)被大規(guī)模采用绣夺,尤其是大型前端項目,我們簡要的改造為一個 TypeScript 版本

可以復(fù)制以下代碼到 TypeScript Playground 體驗運(yùn)行效果

代碼

interface ICallbackList {
  [id: string]: Function;
}

interface IEventObject {
  [eventName: string]: ICallbackList;
}

interface ISubscribe {
  unSubscribe: () => void;
}

interface IEventBus {
  publish<T extends any[]>(eventName: string, ...args: T): void;
  subscribe(eventName: string, callback: Function): ISubscribe;
  subscribeOnce(eventName: string, callback: Function): ISubscribe;
  clear(eventName: string): void;
}

class EventBus implements IEventBus {
  private _eventObject: IEventObject;
  private _callbackId: number;
  constructor() {
    // 初始化事件列表
    this._eventObject = {};
    // 回調(diào)函數(shù)列表的id
    this._callbackId = 0;
  }
  // 發(fā)布事件
  publish<T extends any[]>(eventName: string, ...args: T): void {
    // 取出當(dāng)前事件所有的回調(diào)函數(shù)
    const callbackObject = this._eventObject[eventName];

    if (!callbackObject) return console.warn(eventName + " not found!");

    // 執(zhí)行每一個回調(diào)函數(shù)
    for (let id in callbackObject) {
      // 執(zhí)行時傳入?yún)?shù)
      callbackObject[id](...args);

      // 只訂閱一次的回調(diào)函數(shù)需要刪除
      if (id[0] === "d") {
        delete callbackObject[id];
      }
    }
  }
  // 訂閱事件
  subscribe(eventName: string, callback: Function): ISubscribe {
    // 初始化這個事件
    if (!this._eventObject[eventName]) {
      // 使用對象存儲欢揖,注銷回調(diào)函數(shù)的時候提高刪除的效率
      this._eventObject[eventName] = {};
    }

    const id = this._callbackId++;

    // 存儲訂閱者的回調(diào)函數(shù)
    // callbackId使用后需要自增陶耍,供下一個回調(diào)函數(shù)使用
    this._eventObject[eventName][id] = callback;

    // 每一次訂閱事件,都生成唯一一個取消訂閱的函數(shù)
    const unSubscribe = () => {
      // 清除這個訂閱者的回調(diào)函數(shù)
      delete this._eventObject[eventName][id];

      // 如果這個事件沒有訂閱者了她混,也把整個事件對象清除
      if (Object.keys(this._eventObject[eventName]).length === 0) {
        delete this._eventObject[eventName];
      }
    };

    return { unSubscribe };
  }

  // 只訂閱一次
  subscribeOnce(eventName: string, callback: Function): ISubscribe {
    // 初始化這個事件
    if (!this._eventObject[eventName]) {
      // 使用對象存儲烈钞,注銷回調(diào)函數(shù)的時候提高刪除的效率
      this._eventObject[eventName] = {};
    }

    // 標(biāo)示為只訂閱一次的回調(diào)函數(shù)
    const id = "d" + this._callbackId++;

    // 存儲訂閱者的回調(diào)函數(shù)
    // callbackId使用后需要自增,供下一個回調(diào)函數(shù)使用
    this._eventObject[eventName][id] = callback;

    // 每一次訂閱事件坤按,都生成唯一一個取消訂閱的函數(shù)
    const unSubscribe = () => {
      // 清除這個訂閱者的回調(diào)函數(shù)
      delete this._eventObject[eventName][id];

      // 如果這個事件沒有訂閱者了毯欣,也把整個事件對象清除
      if (Object.keys(this._eventObject[eventName]).length === 0) {
        delete this._eventObject[eventName];
      }
    };

    return { unSubscribe };
  }

  // 清除事件
  clear(eventName: string): void {
    // 未提供事件名稱,默認(rèn)清除所有事件
    if (!eventName) {
      this._eventObject = {};
      return;
    }

    // 清除指定事件
    delete this._eventObject[eventName];
  }
}

// 測試
interface IObj {
  msg: string;
}

type PublishType = [IObj, number];

const eventBus = new EventBus();

// 訂閱事件eventX
eventBus.subscribe("eventX", (obj: IObj, num: number, s: string) => {
  console.log("模塊A", obj, num);
});
eventBus.subscribe("eventX", (obj: IObj, num: number) => {
  console.log("模塊B", obj, num);
});
eventBus.subscribe("eventX", (obj: IObj, num: number) => {
  console.log("模塊C", obj, num);
});

// 發(fā)布事件eventX
eventBus.publish("eventX", { msg: "EventX published!" }, 1);

// 清除
eventBus.clear("eventX");

// 再次發(fā)布事件eventX臭脓,由于已經(jīng)清除酗钞,所有模塊都不會再收到消息了
eventBus.publish<PublishType>("eventX", { msg: "EventX published again!" }, 2);

// 輸出
[LOG]: "模塊A",  {
  "msg": "EventX published!"
},  1
[LOG]: "模塊B",  {
  "msg": "EventX published!"
},  1
[LOG]: "模塊C",  {
  "msg": "EventX published!"
},  1
[WRN]: "eventX not found!"

6. 單例模式

在實(shí)際使用過程中,往往只需要一個事件總線就能滿足需求来累,這里有兩種情況算吩,保持在上層實(shí)例中單例和全局單例。

  1. 保持在上層實(shí)例中單例

將事件總線引入到上層實(shí)例使用佃扼,只需要保證在一個上層實(shí)例中只有一個 EventBus偎巢,如果上層實(shí)例有多個,意味著有多個事件總線兼耀,但是每個上層實(shí)例管控自己的事件總線压昼。
首先在上層實(shí)例中建立一個變量用來存儲事件總線,只在第一次使用時初始化瘤运,后續(xù)其他模塊使用事件總線時直接取得這個事件總線實(shí)例窍霞。

代碼

// 上層實(shí)例
class LWebApp {
  private _eventBus?: EventBus;

  constructor() {}

  public getEventBus() {
    // 第一次初始化
    if (this._eventBus == undefined) {
      this._eventBus = new EventBus();
    }

    // 后續(xù)每次直接取唯一一個實(shí)例,保持在LWebApp實(shí)例中單例
    return this._eventBus;
  }
}

// 使用
const eventBus = new LWebApp().getEventBus();
  1. 全局單例

有時候我們希望不管哪一個模塊想使用我們的事件總線拯坟,我們都想這些模塊使用的是同一個實(shí)例但金,這就是全局單例,這種設(shè)計能更容易統(tǒng)一管理事件郁季。

寫法同上面的類似冷溃,區(qū)別是要把 _eventBusgetEventBus 轉(zhuǎn)為靜態(tài)屬性。使用時無需實(shí)例化 EventBusTool 工具類梦裂,直接使用靜態(tài)方法就行了似枕。

代碼

// 上層實(shí)例
class EventBusTool {
  private static _eventBus?: EventBus;

  constructor() {}

  public static getEventBus(): EventBus {
    // 第一次初始化
    if (this._eventBus == undefined) {
      this._eventBus = new EventBus();
    }

    // 后續(xù)每次直接取唯一一個實(shí)例,保持全局單例
    return this._eventBus;
  }
}

// 使用
const eventBus = EventBusTool.getEventBus();

原文:https://lwebapp.com/zh/post/event-bus

總結(jié)

以上是小編對 Event Bus 的一些理解年柠,基本上實(shí)現(xiàn)了想要的效果凿歼。通過自己動手實(shí)現(xiàn)一遍發(fā)布訂閱模式,也加深了對經(jīng)典設(shè)計模式的理解。其中還有很多不足和需要優(yōu)化的地方答憔,歡迎大家多多分享自己的經(jīng)驗味赃。

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市虐拓,隨后出現(xiàn)的幾起案子心俗,更是在濱河造成了極大的恐慌,老刑警劉巖侯嘀,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異谱轨,居然都是意外死亡戒幔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門土童,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诗茎,“玉大人,你說我怎么就攤上這事献汗「叶” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵罢吃,是天一觀的道長楚午。 經(jīng)常有香客問我,道長尿招,這世上最難降的妖魔是什么矾柜? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮就谜,結(jié)果婚禮上怪蔑,老公的妹妹穿的比我還像新娘。我一直安慰自己丧荐,他們只是感情好缆瓣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著虹统,像睡著了一般弓坞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上车荔,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天昼丑,我揣著相機(jī)與錄音,去河邊找鬼夸赫。 笑死菩帝,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播呼奢,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼宜雀,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了握础?” 一聲冷哼從身側(cè)響起辐董,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎禀综,沒想到半個月后简烘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡定枷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年孤澎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片欠窒。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡覆旭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出岖妄,到底是詐尸還是另有隱情型将,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布荐虐,位于F島的核電站七兜,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏福扬。R本人自食惡果不足惜惊搏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望忧换。 院中可真熱鬧恬惯,春花似錦、人聲如沸亚茬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刹缝。三九已至碗暗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間梢夯,已是汗流浹背言疗。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留颂砸,地道東北人噪奄。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓死姚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親勤篮。 傳聞我的和親對象是個殘疾皇子都毒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容