持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第18天,点击查看活动详情
什么是发布-订阅模式
发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知
常见的例子:比如微信公众号,当作者发布了一篇文章,就会立即给你发布一篇文章。
- 读者不需要天天都都追着作者询问他有没有更新,当文章更新了读者立马就知道了,读者什么时候看就和作者没有很大的关心了
- 读者和作者没有那么强的耦合,有可能作者实现了财富自由由其他人代笔,但是这并不影响读者继续阅读
又比如:异步编程中订阅 ajax
请求的 error、success
等事件,中间的过程我们并不关心,我们只需要知道他完成的时间点。
实现一个订阅发布模式
一个简单的 dom
事件,我们并不知道用户什么时候点击,当body
被点击的时候,body
节点便会向订阅者发布这个消息。
document.body.addEventListener(
'click',
function () {
alert(2);
},
false
);
document.body.click(); // 模拟用户点击
我们来实现一个发布-订阅模式
- 建立一个消息中心:负责存储消息与订阅者的对应关系,有消息触发时,负责通知订阅者
- 建立订阅者:去消息中心订阅自己需要的消息
- 建立发布者:满足条件时,通过消息中心发布消息
class PubSub {
constructor() {
// 存放订阅消息
this.events = {}; // { key: [] }
}
// 订阅
subscribe(event, fn) {
if (this.events.hasOwnProperty(event)) {
// 已经订阅了
this.events[event].push(fn);
} else {
// 没有订阅
this.events[event] = [fn];
}
}
publish(event, agrs) {
// 去除当前订阅者的订阅回调
const subscribedEvents = this.events[event];
if (subscribedEvents && subscribedEvents.length > 0) {
subscribedEvents.forEach((callback) => {
callback.call(this, agrs);
});
}
}
}
var pub = new PubSub();
pub.subscribe('order', function (event) {
console.log('张三', event);
});
pub.subscribe('order', function (event) {
console.log('李四', event);
});
pub.publish('order', '消费 100¥');
// 张三 消费 100¥
// 李四 消费 100¥
我们再来添加一个取消订阅的方法
unsubscribe(event, fn) {
// 删除某个订阅,保留其他订阅
const subscribedEvents = this.events[event];
if (subscribedEvents && subscribedEvents.length) {
if(!fn){
// 如果 fn 不存在,就清除对应 event 的整个队列
this.events[event] = [];
return
}
this.events[event] = this.events[event].filter((cb) => cb !== fn);
}
}
完整代码
class PubSub {
constructor() {
// 存放订阅消息
this.events = {};
}
// 订阅
subscribe(event, fn) {
if (this.events.hasOwnProperty(event)) {
// 已经订阅了
this.events[event].push(fn);
} else {
// 没有订阅
this.events[event] = [fn];
}
}
// 改写了这里, ...agrs 获取剩余所有参数
publish(event, ...agrs) {
// 当前订阅者的订阅回调
const subscribedEvents = this.events[event];
if (subscribedEvents && subscribedEvents.length > 0) {
subscribedEvents.forEach((callback) => {
callback.call(this, ...agrs);
});
}
}
unsubscribe(event, fn) {
// 删除某个订阅,保留其他订阅
const subscribedEvents = this.events[event];
if (subscribedEvents && subscribedEvents.length) {
if (!fn) {
// 如果 fn 不存在,就清除对应 event 的整个队列
this.events[event] = [];
return;
}
this.events[event] = this.events[event].filter((cb) => cb !== fn);
}
}
}
var pub = new PubSub();
pub.subscribe('order', function (...event) {
console.log('张三', ...event);
});
pub.subscribe('order', function (event) {
console.log('李四', event);
});
pub.publish('order', '消费100¥', '买了蛋糕');
// 张三 消费100¥ 买了蛋糕
// 李四 消费100¥
小结
优点
- 对象之间解耦
- 异步编程中,可以更松耦合的代码编写
缺点
- 创建订阅者本身要消耗一定的时间和内存
- 虽然可以弱化对象之间的联系,多个发布者和订阅者嵌套一起的时候,程序难以跟踪维护
观察者模式:观察者(Observer
)直接订阅(Subscribe
)主题(Subject
),而当主题被激活的时候,会触发(Fire Event
)观察者里的事件。
发布订阅模式:订阅者(Subscriber
)把自己想订阅的事件注册(Subscribe
)到调度中心(Event Channel
),当发布者(Publisher
)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。
观察者模式与发布订阅模式的区别:
- 通信方式:在观察者模式中,观察者是知道
Subject
的,Subject
一直保持对观察者进行记录。然而,在发布订阅模式中,先订阅再发布,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。 - 组件耦合:在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。
- 同步异步:观察者模式较多时后是同步的,比如当事件触发,
Subject
就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。 - 使用模式:观察者模式在单个应用程序实现,而发布-订阅更像交叉应用模式。
观察者模式
为了区分发布订阅模式,我们也来实现一个观察者模式
class Observer {
constructor() {
// 存放订阅者
this.observers = [];
// {
// observer: obj,
// action: () => {}
// }
}
addObserver(observer, action) {
// 将观察者和回调放入数组
this.observers.push({ observer, action });
}
notify(...args) {
// 执行每个观察者的回调
this.observers.forEach((item) => {
const { observer, action } = item;
action.call(observer, ...args);
});
}
}
const observer = new Observer();
// 添加观察者
observer.addObserver({ name: '张三' }, function (msg) {
console.log(this.name + '赚了' + msg);
});
observer.addObserver({ name: '李四' }, function (msg) {
console.log(this.name + '赚了' + msg);
});
// 通知所有观察者
observer.notify('一个小目标');
// 张三赚了一个小目标
// 李四赚了一个小目标