设计模式(二)发布订阅模式

257 阅读2分钟

观察者模式中,Subject 直接触及到订阅者(自己维护观察者列表进行注册和通知)。

image-20220130170413954

发布订阅模式则是不直接触及到订阅者、而是由统一的第三方来完成实际的通信的操作

image-20220130171806687

实现一个事件总线 mitt / eventBus

mitt 的使用:

  1. 创建一个 mitt
import mitt from 'mitt'const emitter = mitt()
  1. 订阅事件
emitter.on('foo', callback)
  1. 发布事件
// params 指事件被触发时回调函数接收的入参
emitter.emit('foo', params)

函数式实现:

function mitt() {
  const handlers = {};
  return {
    handlers,
​
    on(eventName, callback) {
      if (!handlers[eventName]) {
        handlers[eventName] = [];
      }
      handlers[eventName].push(callback);
    },
​
    emit(eventName, ...args) {
      if (handlers[eventName]) {
        handlers[eventName].slice().forEach((callback) => callback(...args));
      }
    },
​
    off(eventName, callback) {
      const handlersQueue = handlers[eventName];
      const index = handlersQueue.indexOf(callback);
      if (index !== -1) {
        handlersQueue.splice(index, 1);
      }
    },
​
    once(eventName, callback) {
      const onceCallback = (...args) => {
        callback(...args);
        this.off(eventName, onceCallback);
      };
      this.on(eventName, onceCallback);
    },
  };
}

类实现:

class Mitt {
  constructor() {
    // handler 是一个事件和回调之间的映射表
    this.handlers = {};
  }
​
  // on方法用于安装事件监听器
  on(eventName, callback) {
    if (!this.handlers[eventName]) {
      this.handlers[eventName] = [];
    }
    this.handlers[eventName].push(callback);
  }
​
  // emit方法用于触发目标事件
  emit(eventName, ...args) {
    if (this.handlers[eventName]) {
      this.handlers[eventName].slice().forEach((callback) => callback(...args));
    }
  }
​
  // 移除某个事件回调队列里的指定回调函数
  off(eventName, callback) {
    const handlers = this.handlers[eventName];
    const index = handlers.indexOf(callback);
    if (index !== -1) {
      handlers.splice(index, 1);
    }
  }
​
  // 为事件注册单次监听器
  once(eventName, callback) {
    // 对回调函数进行包装,使其执行完毕自动被移除
    const onceCallback = (...args) => {
      callback(...args);
      this.off(eventName, onceCallback);
    };
    this.on(eventName, onceCallback);
  }
}

与观察者模式对比

观察者模式,解决的其实是模块间的耦合问题,有它在,即便是两个分离的、毫不相关的模块,也可以实现数据通信。但观察者模式仅仅是减少了耦合,并没有完全地解决耦合问题——被观察者必须去维护一套观察者的集合,这些观察者必须实现统一的方法供被观察者调用。

发布-订阅模式,则是发布者完全不用感知订阅者,不用关心它怎么实现回调方法,事件的注册和触发都发生在独立于双方的第三方平台(事件总线)上。在发布-订阅模式下,实现了完全地解耦。

但这并不意味着,发布-订阅模式就比观察者模式“高级”。 在实际开发中,我们的模块解耦诉求并非总是需要它们完全解耦,因此需灵活运用。

参考:

JavaScript 设计模式核⼼原理与应⽤实践

前端的设计模式系列