手写代码:用js实现发布订阅模式

81 阅读2分钟

概念

这本书描述的比较清楚,不做赘述: 《Javascript设计模式与开发实践》

实现

核心API

  1. on 添加一个事件监听
  2. once 添加一个事件监听,当触发一次后就移除监听
  3. emit 触发事件
  4. off 移除事件监听

基本结构

interface IEvent {
  listener: Function;
  once: boolean;
}
interface Events {
  [eventName: string]: IEvent[];
}

class EventBus {
  events: Events = {}; // 缓存所有添加的事件

  // 触发事件
  emit(eventName: string, ...args) {}

  // 添加一个事件监听
  on(eventName: string, listener: Function, once?: boolean) {}

  // 添加一个事件监听,当触发一次后就移除监听
  once(eventName: string, listener: Function) {}

  // 移除事件监听
  off(eventName: string, listener?: Function) {}
}

基本实现

interface IEvent {
  listener: Function;
  once: boolean;
}
interface Events {
  [eventName: string]: IEvent[];
}

class EventBus {
  events: Events = {}; // 缓存所有添加的事件

  // 触发事件
  emit(eventName: string, ...args) {
    if (!this.events[eventName]) return;
    for (const event of this.events[eventName]) {
      const { listener, once } = event;
      listener(...args);
      if (once) {
        this.off(eventName, listener);
      }
    }
  }

  // 添加一个事件监听
  on(eventName: string, listener: Function, once?: boolean) {
    const event = { listener, once: !!once };
    // 已添加
    if (this.events[eventName]) {
      this.events[eventName].push(event);
    } else {
      this.events[eventName] = [event];
    }
  }

  // 添加一个事件监听,当触发一次后就移除监听
  once(eventName: string, listener: Function) {
    this.on(eventName, listener, true);
  }

  // 移除事件监听
  off(eventName: string, listener?: Function) {
    if (!this.events[eventName]) return;
    if (listener) {
      this.events[eventName] = this.events[eventName].filter(
        (event) => event?.listener !== listener
      );
    } else {
      delete this.events[eventName];
    }
  }
}
// on方法中判断是否已经添加了该事件的逻辑,可以不用if判断,也可以这样写
on(eventName: string, listener: Function, once?: boolean) {
    const event = { listener, once: !!once };
    // 已添加
    // if (this.events[eventName]) {
    //   this.events[eventName].push(event);
    // } else {
    //   this.events[eventName] = [event];
    // }
    // 不用if,更简洁的写法
    this.events[eventName] = this.events[eventName] || [];
    this.events[eventName].push(event);
  }

单例模式

通常一个简单的项目使用一个实例做全局的通信就够了,过度使用发布订阅模式代码不易维护且不好排查问题。甲new一个,乙new一个,丙添加到甲,触发到乙,丙迷茫了开始排查问题……

interface IEvent {
  listener: Function;
  once: boolean;
}
interface Events {
  [eventName: string]: IEvent[];
}

class EventBus {
  events: Events = {};
  
  private static instance: EventBus;

  static getInstance() {
    if (!EventBus.instance) {
      EventBus.instance = new EventBus();
    }
    return EventBus.instance;
  }

  emit(eventName: string, ...args) {}

  on(eventName: string, listener: Function, once?: boolean) {}

  once(eventName: string, listener: Function) {}

  off(eventName: string, listener?: Function) {}
}

// 注意这里导出的是:
export default EventBus.getInstance();

多说点啥

发布订阅模式工作中经常会用到,比如IM通信、微前端子应用通信、Vue的$on $emit、Node的EventBus…… 理解其原理为高效工作和阅读源码打好基础。Happy coding!