Skip to content

发布订阅是一种通过缓存回调事件延后执行的操作

我们可以简单的把这种做法当作是解耦回调函数,分别定义触发者绑定回调函数再手动触发

有点我们处理异步事件那味儿

👇 我们跟回调函数比较来理解发布订阅模式存在的意义

  • 回调函数
    • 事件A执行完之后自动执行事件B
  • 观察者
    • 事件A执行完之后,手动执行多个事件
    • 因为数据结构导致一个工具方法 只能有一个触发者
  • 发布订阅
    • 事件A执行完之后,手动执行多个事件
    • 因为数据结构导致一个工具方法 可以有多个不同的触发者

实现发布订阅

js
class 调度中心 {
  constructor() {
    this.订阅者列表 = {}
  }
  订阅(订阅物,订阅者) {
    this.订阅者列表[订阅物].push(订阅者)
  }
  发布(订阅物) {
    this.订阅者列表[订阅物].forEach(item => item())
  }
  取消订阅(订阅物) {
    this.订阅者列表[订阅物] = []
  }
}

const 调度中心A = const new 调度中心()
调度中心A.订阅('a', () => console.lgo('收到发布'))
调度中心A.发布('a')
js
class Observer {
  constructor() {
    this.message = {} // 消息队列
  }

  $on(type, callback) {
    // 如果没有这个属性,就初始化一个空的数组
    if (!this.message[type]) {
      this.message[type] = [];
    }
    this.message[type].push(callback);
  }

  $off(type, callback) {
    if (!this.message[type]) return;
    // 判断是否有callback这个参数
    if (!callback) {
      this.message[type] = undefined;
      return;
    }
    // 如果有callback,就仅仅删掉callback这个消息(过滤掉这个消息方法)
    this.message[type] = this.message[type].filter((item) => item !== callback);
  }
  $emit(type) {
    if(!this.message[type]) return;
    // 挨个执行每一个消息的回调函数callback
    this.message[type].forEach(item => {
      item()
    });
  }
}
const person1 = new Observer();
person1.$on('buy', handlerA);
person1.$on('buy', handlerB);
person1.$on('buy', handlerC);
console.log('person1 :>> ', person1);
person1.$off('buy',handlerC);
console.log('person1 :>> ', person1);

实现观察者

个人觉得观察者发布订阅是一个东西

都是通过缓存回调事件延后执行

只是观察者简单一点的数据结构造就了两者的不同,思路都是一样的

观察者是一对多的关系,因此不需要维护 { name1: [callback...]},而是直接事件数组就行 [callback1,...] 并且触发订阅者也不需要指定name

js
class 发布者 {
  constructor() {
    this.观察者列表 = []
  }
  添加观察者(订阅者) {
    this.观察者列表.push(订阅者)
  }
  发布() {
    this.观察者列表.forEach(item => item())
  }
  取消观察() {
    this.观察者列表 = []
  }
}

订阅者是实例时

在 👆 我们的订阅者、观察者都是一个回调函数

在实际场景订阅者、观察者更可能是一个实例

这时候我们规定这个实例上都带着一个 callback 让我们发布时触发函数即可

如👇观察者带着 update 函数

js
// 观察者
class Observer {
  constructor(cb){
    if (typeof cb === 'function') {
      this.cb = cb
    } else {
      throw new Error('Observer构造器必须传入函数类型!')
    }
  }
  update() {
    this.cb()
  }
}

// 目标对象
class Subject {
  constructor() {
    // 维护观察者列表
    this.observerList = []
  }
  addObserver(observer) {
    this.observerList.push(observer)
  }
  notify() {
    this.observerList.forEach(observer => {
      observer.update() // 发布遍历触发的是实例里的update
    })
  }
}

const observerCallback = function() {
  console.log('我被通知了')
}
const observer = new Observer(observerCallback)
const subject = new Subject();
subject.addObserver(observer);
subject.notify();

实现EventBus

我们做 vue 的组件通信有时也会用到 EventBus

js
// 组件a
const Bus = new Vue()
Bus.$on('a', () => console.log('回调函数'))

// 组件b
const Bus = new Vue()
Bus.$emit('a')

EventBus 就是一个发布订阅

js
class Vue {
  constructor() {
    this.events = {}
  }
  $on(type, callback) {
    // 如果没有这个属性,就初始化一个空的数组
    if (!this.events[type]) {
      this.events[type] = [];
    }
    this.events[type].push(callback);
  }
  $emit(type) {
    if(!this.events[type]) return;
    // 挨个执行每一个消息的回调函数callback
    this.events[type].forEach(item => {
      item()
    });
  }
}

另外,DOM的事件委托(事件监听DOM.addEventListener)就是一种发布订阅

参考资料