文章目录
- 简介
- 特点
- 流程
- 通俗举例
- 角色对应
- 运行流程
- 1. 订阅(Subscribe)
- 2. 发布(Publish)
- 3. 通知(Notify)
- 关键点
- 代码实现
- 注意事项
简介
发布-订阅模式是一种消息传递模式,它允许发送者(发布者)发送消息而不关心谁将接收这些消息,同样地,接收者(订阅者)可以监听他们感兴趣的消息,而不需要知道这些消息来自哪里。这种模式在JavaScript中常用于实现事件处理系统,如DOM事件监听或自定义事件系统。
特点
-
解耦:发布者和订阅者之间不直接依赖,它们之间通过事件中心(Event Center)进行通信,实现了解耦。
-
一对多:一个事件可以有多个订阅者,当事件被发布时,所有订阅了该事件的订阅者都会收到通知。
-
异步通信:发布者和订阅者之间的通信是异步的,发布者发布事件时不会等待订阅者的处理结果。
-
扩展性好:新的订阅者可以随时订阅事件,旧的订阅者也可以随时取消订阅,无需修改发布者代码。
流程
-
定义事件总线:首先,需要定义一个事件中心对象,该对象用于管理事件和订阅者。
-
订阅事件:订阅者通过调用事件中心的订阅方法,指定要订阅的事件类型和回调函数。
-
发布事件:发布者通过调用事件中心的发布方法,并传递事件类型和相关数据来触发事件。
-
触发回调:事件中心接收到发布请求后,会遍历该事件类型下的所有订阅者,并依次调用它们的回调函数,传递事件数据。
通俗举例
以下将使用消费者
、邮局
、订阅报纸
和 邮递员
来通俗举例解释发布-订阅模式的关系:
角色对应
-
发布者:报纸出版社(负责发布报纸内容)
-
事件:报纸内容(如新闻、广告等)
-
事件总线:邮局(负责传递报纸)
-
订阅者:消费者(订阅报纸的人)
-
邮递员:邮局的工作人员,负责将报纸从邮局传递到消费者手中
运行流程
1. 订阅(Subscribe)
-
消费者(订阅者)到邮局(事件总线)填写订阅单,指定他们感兴趣的报纸(事件类型)。
-
邮局(事件总线)记录消费者的订阅信息,包括消费者的地址和所订阅的报纸类型。
2. 发布(Publish)
-
报纸出版社(发布者)完成当天的报纸编辑,将报纸内容(事件)发送到邮局(事件总线)。
-
邮局(事件总线)接收到报纸后,会检查哪些消费者订阅了这份报纸。
3. 通知(Notify)
-
邮局(事件总线)将报纸(事件)交给邮递员(传递者)。
-
邮递员根据消费者的订阅信息,将报纸投递到消费者的地址(即通知订阅者)。
关键点
-
松耦合:消费者(订阅者)和报纸出版社(发布者)之间不直接通信,而是通过邮局(事件总线)进行间接通信。
-
事件类型:消费者(订阅者)可以订阅不同类型的报纸(如日报、晚报、财经报等),这对应于不同的事件类型。
-
异步性:消费者(订阅者)可以在报纸发布之前或之后订阅,邮局(事件总线)会负责将后续的报纸投递给已经订阅的消费者。
代码实现
// 创建一个事件中心(模拟邮局)
class EventCenter {
constructor() {
this.subscribers = {}; // 订阅者列表,按事件类型存储
}
// 订阅事件(消费者订阅报纸)
subscribe (eventType, callback) {
if (!this.subscribers[eventType]) {
this.subscribers[eventType] = [];
}
this.subscribers[eventType].push(callback);
}
// 取消订阅事件(消费者取消订阅报纸)
unsubscribe(eventType, callback) {
if (this.subscribers[eventType]) {
const index = this.subscribers[eventType].indexOf(callback);
if (index !== -1) {
this.subscribers[eventType].splice(index, 1);
}
}
}
// 发布事件(报纸出版社发布报纸)
publish (eventType, data) {
// 如果有订阅者订阅了这个事件
if (this.subscribers[eventType]) {
// 遍历所有订阅者,并通知他们(传递报纸)
this.subscribers[eventType].forEach(callback => {
callback(data);
});
}
}
// 清除所有订阅者
clearAllSubscriptions () {
this.subscribers = {};
}
}
// 创建一个事件中心实例
const eventCenter = new EventCenter();
// 消费者(订阅者)
function consumerA (newspaper) {
console.log('消费者A收到了报纸:', newspaper);
}
function consumerB (newspaper) {
console.log('消费者B收到了报纸:', newspaper);
}
// 消费者A订阅日报
eventCenter.subscribe('日报', consumerA);
// 消费者B也订阅日报
eventCenter.subscribe('日报', consumerB);
// 报纸出版社(发布者)发布日报
eventCenter.publish('日报', '今天的头条新闻...');
// 如果消费者C稍后订阅,他将不会收到之前发布的日报
function consumerC (newspaper) {
console.log('消费者C收到了报纸:', newspaper);
}
// 消费者C订阅日报(但日报已经发布过了,所以消费者C不会收到这次的报纸)
eventCenter.subscribe('日报', consumerC);
// 如果报纸出版社再次发布日报,消费者C将会收到
eventCenter.publish('日报', '明天的头条预告...');
// 假设消费者A后来决定不再订阅日报
eventCenter.unsubscribe('日报', consumerA);
// 报纸出版社再次发布日报,消费者A将不会收到,但消费者B和消费者C将会收到
eventCenter.publish('日报', '后天的头条预告...');
// 清除所有订阅者
eventCenter.clearAllSubscriptions();
// 尝试发布事件,但此时没有订阅者
eventCenter.publish('日报', '没有订阅者将收到此消息');
注意事项
-
内存管理:确保在不再需要某个订阅者时取消订阅,以避免内存泄漏。
-
事件命名:为事件类型选择具有描述性和唯一性的名称,以避免命名冲突。
-
错误处理:在回调函数和事件中心的方法中添加适当的错误处理逻辑,以确保程序的健壮性。
-
避免无限循环:确保在回调函数中不会触发相同的事件,这可能导致无限循环。
-
性能考虑:当订阅者数量较多或事件发布频率较高时,需要考虑性能优化,如使用WeakMap来存储订阅者等。
-
兼容性:在浏览器中实现时,要注意不同浏览器对事件处理的支持情况,确保代码具有良好的兼容性。