发布订阅是一种通过缓存回调事件延后执行的操作
我们可以简单的把这种做法当作是解耦回调函数,分别定义触发者,绑定回调函数,再手动触发
有点我们处理异步事件那味儿
👇 我们跟回调函数比较来理解发布订阅模式存在的意义
- 回调函数
- 事件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
)就是一种发布订阅