前端面試題:JS 如何實(shí)現(xiàn)事件總線 Event Bus

原文:https://dushusir.com/js-event-bus/

介紹

Event Bus 事件總線岁诉,通常作為多個(gè)模塊間的通信機(jī)制跋选,相當(dāng)于一個(gè)事件管理中心前标,一個(gè)模塊發(fā)送消息,其它模塊接受消息只搁,就達(dá)到了通信的作用俭尖。

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

原理

Event Bus 本質(zhì)上是采用了發(fā)布-訂閱的設(shè)計(jì)模式陷猫,比如多個(gè)模塊 AB足陨、C 訂閱了一個(gè)事件 EventX墨缘,然后某一個(gè)模塊 X 在事件總線發(fā)布了這個(gè)事件零抬,那么事件總線會(huì)負(fù)責(zé)通知所有訂閱者 A平夜、BC玩裙,它們都能收到這個(gè)通知消息段直,同時(shí)還可以傳遞參數(shù)坷牛。

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

分析

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

  • 首先構(gòu)造一個(gè) EventBus 類京闰,初始化一個(gè)空對(duì)象用于存放所有的事件
  • 在接受訂閱時(shí),將事件名稱作為 key 值俏站,將需要在接受發(fā)布消息后執(zhí)行的回調(diào)函數(shù)作為 value 值痊土,由于一個(gè)事件可能有多個(gè)訂閱者,所以這里的回調(diào)函數(shù)要存儲(chǔ)成列表
  • 在發(fā)布事件消息時(shí)犯祠,從事件列表里取得指定的事件名稱對(duì)應(yīng)的所有回調(diào)函數(shù)衡载,依次觸發(fā)執(zhí)行即可

以下是代碼詳細(xì)實(shí)現(xiàn)痰娱,可以復(fù)制到谷歌瀏覽器控制臺(tái)直接運(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í)行每一個(gè)回調(diào)函數(shù)
    for (let callback of callbackList) {
      callback();
    }
  }
  // 訂閱事件
  subscribe(eventName, callback) {
    // 初始化這個(gè)事件
    if (!this.eventObject[eventName]) {
      this.eventObject[eventName] = [];
    }

    // 存儲(chǔ)訂閱者的回調(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í)傳遞參數(shù)

發(fā)布者傳入一個(gè)參數(shù)到 EventBus 中政鼠,在 callback 回調(diào)函數(shù)執(zhí)行的時(shí)候接著傳出參數(shù),這樣每一個(gè)訂閱者就可以收到參數(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í)行每一個(gè)回調(diào)函數(shù)
    for (let callback of callbackList) {
      // 執(zhí)行時(shí)傳入?yún)?shù)
      callback(...args);
    }
  }
  // 訂閱事件
  subscribe(eventName, callback) {
    // 初始化這個(gè)事件
    if (!this.eventObject[eventName]) {
      this.eventObject[eventName] = [];
    }

    // 存儲(chǔ)訂閱者的回調(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. 訂閱后如何取消訂閱

有時(shí)候訂閱者只想在某一個(gè)時(shí)間段訂閱消息瞬雹,這就涉及帶取消訂閱功能酗捌。我們將對(duì)代碼進(jìn)行改造涌哲。

首先阀圾,要實(shí)現(xiàn)指定訂閱者取消訂閱,每一次訂閱事件時(shí)涡真,都生成唯一一個(gè)取消訂閱的函數(shù),用戶直接調(diào)用這個(gè)函數(shù)缸剪,我們就把當(dāng)前訂閱的回調(diào)函數(shù)刪除橄登。

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

其次萄喳,訂閱的回調(diào)函數(shù)列表使換成對(duì)象結(jié)構(gòu)存儲(chǔ)蹋半,為每一個(gè)回調(diào)函數(shù)設(shè)定一個(gè)唯一 id减江, 注銷回調(diào)函數(shù)的時(shí)候可以提高刪除的效率,如果還是使用數(shù)組的話需要使用 split 刪除份企,效率不如對(duì)象的 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í)行每一個(gè)回調(diào)函數(shù)
    for (let id in callbackObject) {
      // 執(zhí)行時(shí)傳入?yún)?shù)
      callbackObject[id](...args);
    }
  }
  // 訂閱事件
  subscribe(eventName, callback) {
    // 初始化這個(gè)事件
    if (!this.eventObject[eventName]) {
      // 使用對(duì)象存儲(chǔ)降宅,注銷回調(diào)函數(shù)的時(shí)候提高刪除的效率
      this.eventObject[eventName] = {};
    }

    const id = this.callbackId++;

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

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

      // 如果這個(gè)事件沒有訂閱者了瘸恼,也把整個(gè)事件對(duì)象清除
      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不會(huì)再收到消息了
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. 如何只訂閱一次

如果一個(gè)事件只發(fā)生一次冰啃,通常也只需要訂閱一次,收到消息后就不用再接受消息焚刚。

首先扇调,我們提供一個(gè) subscribeOnce 的接口狼钮,內(nèi)部實(shí)現(xiàn)幾乎和 subscribe 一樣熬芜,只有一個(gè)地方有區(qū)別,在 callbackId 前面的加一個(gè)字符 d瑞侮,用來標(biāo)示這是一個(gè)需要?jiǎng)h除的訂閱鼓拧。

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

然后季俩,在執(zhí)行回調(diào)函數(shù)后判斷當(dāng)前回調(diào)函數(shù)的 id 有沒有標(biāo)示,決定我們是否需要?jiǎng)h除這個(gè)回調(diào)函數(shù)藐鹤。

// 只訂閱一次的回調(diào)函數(shù)需要?jiǎng)h除
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í)行每一個(gè)回調(diào)函數(shù)
    for (let id in callbackObject) {
      // 執(zhí)行時(shí)傳入?yún)?shù)
      callbackObject[id](...args);

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

    const id = this.callbackId++;

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

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

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

    return { unSubscribe };
  }

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

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

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

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

      // 如果這個(gè)事件沒有訂閱者了,也把整個(gè)事件對(duì)象清除
      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只訂閱了一次看尼,不會(huì)再收到消息了
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. 如何清除某個(gè)事件或者所有事件

我們還希望通過一個(gè) clear 的操作來將指定事件的所有訂閱清除掉藏斩,這個(gè)通常在一些組件或者模塊卸載的時(shí)候用到灾茁。

  // 清除事件
  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í)行每一個(gè)回調(diào)函數(shù)
    for (let id in callbackObject) {
      // 執(zhí)行時(shí)傳入?yún)?shù)
      callbackObject[id](...args);

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

    const id = this.callbackId++;

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

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

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

    return { unSubscribe };
  }

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

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

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

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

      // 如果這個(gè)事件沒有訂閱者了忍些,也把整個(gè)事件對(duì)象清除
      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)清除隙券,所有模塊都不會(huì)再收到消息了
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ī)模采用闹司,尤其是大型前端項(xiàng)目游桩,我們簡要的改造為一個(gè) TypeScript 版本

可以復(fù)制以下代碼到 TypeScript Playground 體驗(yàn)運(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í)行每一個(gè)回調(diào)函數(shù)
    for (let id in callbackObject) {
      // 執(zhí)行時(shí)傳入?yún)?shù)
      callbackObject[id](...args);

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

    const id = this._callbackId++;

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

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

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

    return { unSubscribe };
  }

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

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

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

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

      // 如果這個(gè)事件沒有訂閱者了,也把整個(gè)事件對(duì)象清除
      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)清除博个,所有模塊都不會(huì)再收到消息了
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í)際使用過程中,往往只需要一個(gè)事件總線就能滿足需求往堡,這里有兩種情況共耍,保持在上層實(shí)例中單例和全局單例。

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

將事件總線引入到上層實(shí)例使用穆咐,只需要保證在一個(gè)上層實(shí)例中只有一個(gè) EventBus字旭,如果上層實(shí)例有多個(gè),意味著有多個(gè)事件總線拍柒,但是每個(gè)上層實(shí)例管控自己的事件總線斤儿。
首先在上層實(shí)例中建立一個(gè)變量用來存儲(chǔ)事件總線恐锦,只在第一次使用時(shí)初始化疆液,后續(xù)其他模塊使用事件總線時(shí)直接取得這個(gè)事件總線實(shí)例堕油。

代碼

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

  constructor() {}

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

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

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

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

寫法同上面的類似绊汹,區(qū)別是要把 _eventBusgetEventBus 轉(zhuǎn)為靜態(tài)屬性西乖。使用時(shí)無需實(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ù)每次直接取唯一一個(gè)實(shí)例被廓,保持全局單例
    return this._eventBus;
  }
}

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

原文:https://dushusir.com/js-event-bus/

總結(jié)

以上是小編對(duì) Event Bus 的一些理解嫁乘,基本上實(shí)現(xiàn)了想要的效果球碉。通過自己動(dòng)手實(shí)現(xiàn)一遍發(fā)布訂閱模式睁冬,也加深了對(duì)經(jīng)典設(shè)計(jì)模式的理解。其中還有很多不足和需要優(yōu)化的地方直奋,歡迎大家多多分享自己的經(jīng)驗(yàn)施禾。

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末弥搞,一起剝皮案震驚了整個(gè)濱河市攀例,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌挖胃,老刑警劉巖冠骄,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凛辣,死亡現(xiàn)場離奇詭異,居然都是意外死亡防泵,警方通過查閱死者的電腦和手機(jī)蝗敢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門寿谴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來讶泰,“玉大人,你說我怎么就攤上這事码泞±欠福” “怎么了悯森?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵呐馆,是天一觀的道長莲兢。 經(jīng)常有香客問我,道長收班,這世上最難降的妖魔是什么摔桦? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任邻耕,我火速辦了婚禮,結(jié)果婚禮上啼辣,老公的妹妹穿的比我還像新娘御滩。我一直安慰自己削解,他們只是感情好富弦,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著氛驮,像睡著了一般腕柜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上矫废,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天媳握,我揣著相機(jī)與錄音,去河邊找鬼磷脯。 笑死蛾找,一個(gè)胖子當(dāng)著我的面吹牛赵誓,可吹牛的內(nèi)容都是我干的打毛。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼俩功,長吁一口氣:“原來是場噩夢啊……” “哼幻枉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起诡蜓,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤熬甫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蔓罚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體椿肩,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年豺谈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了郑象。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡茬末,死狀恐怖厂榛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤击奶,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布辈双,位于F島的核電站,受9級(jí)特大地震影響柜砾,放射性物質(zhì)發(fā)生泄漏辐马。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一局义、第九天 我趴在偏房一處隱蔽的房頂上張望喜爷。 院中可真熱鬧,春花似錦萄唇、人聲如沸檩帐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽湃密。三九已至,卻和暖如春四敞,著一層夾襖步出監(jiān)牢的瞬間泛源,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工忿危, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留达箍,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓铺厨,卻偏偏與公主長得像缎玫,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子解滓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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