什么是发布订阅模式?
发布订阅模式也被称为观察者模式,这个模式中有两种角色:发布者(被观察者)
和 订阅者(观察者)
。
通常的操作是:订阅者订阅发布者的某一个事件,发布者接收到这个事件变化的时候,通知所有的订阅者。
举个栗子:你在微信中关注了一个公众号,这个公众号在更新文章的时候,微信会将新的文章推送到你的微信消息中,公众号是发布者,你的微信号是订阅者。
你可以关注多个公众号,公众号也可以有多个粉丝。
一个简单的观察者类
class Observer {/*** 事件队列*/static events = {};/*** 订阅* @param {*} topic* @param {*} callback*/static subscribe(topic, callback) {if (!Observer.events[topic]) {Observer.events[topic] = [];}Observer.events[topic].push(callback);return () => {const index = Observer.events[topic].findIndex((item) => item === callback);Observer.events[topic].splice(index, 1);};}/*** 发布* @param {*} topic* @param {*} data*/static publish(topic, data) {if (Observer.events[topic]) {Observer.events[topic].forEach((fn) => {fn(data);});} else {console.error(`topic [${topic}] is empty!`);}}
}
订阅消息:
// 订阅消息
const unsubscribe = Observer.subscribe("update", (data) => {console.log(data);
});// 取消订阅
unsubscribe();
发布消息:
Observer.publish("update", 1);
可以接收历史消息的观察者类
上面的观察者无法接收历史消息,如果一个消息在未订阅时就已经发布,那么这条消息就会被漏掉。
一个可能的场景是:在一个页面中,需要在导航栏中展示用户信息,用户信息需要通过网络请求来获取,获取到后通过 Observer.publish()
方法发布,导航栏通过 Observer.subscribe()
方法来获取用户信息及回调。
这个场景中如果用户信息已经获取,但是导航栏组件还未加载,这种情况下导航栏就再也拿不到用户信息了。
解决方案就是维护一个历史消息列表,在调用 Observer.subscribe()
订阅消息时如果有历史消息则立即触发回调。
class Observer {/*** 历史消息*/static history = {};/*** 事件队列*/static events = {};/*** 订阅* @param {*} topic* @param {*} callback*/static subscribe(topic, callback) {if (!Observer.events[topic]) {Observer.events[topic] = [];}Observer.events[topic].push(callback);// 如果有历史消息if (Observer.history[topic]) {callback(Observer.history[topic]);}return () => {const index = Observer.events[topic].findIndex((item) => item === callback);Observer.events[topic].splice(index, 1);};}/*** 发布* @param {*} topic* @param {...any} args*/static publish(topic, data) {if (Observer.events[topic]) {Observer.events[topic].forEach((fn) => {fn(data);});}if (!Observer.history[topic]) {Observer.history[topic] = [];}// 保存历史消息Observer.history[topic].push(data);}
}
可以先发布后订阅:
Observer.publish("update", 222);
Observer.publish("update", 333);const unsubscribe = Observer.subscribe("update", (data) => {if (Array.isArray(data)) {// 历史消息列表} else {// 最新消息}console.log(data);
});Observer.publish("update", 444);
总结
发布订阅模式的优点是可以很方便的实现不同模块之间的通信。
它的缺点在于,观察者对象本身是占用内存的,而且当你订阅一个消息后,也许此消息再也没有发布过,但这个观察者对象会始终存在于内存中。
发布订阅模式弱化了对象之间的联系,但如果过度使用的话,对象和对象之间的必要联系也将被深埋在背后,会导致程序难以跟踪维护和理解。
当多个发布者和订阅者嵌套到一起的时候,很难捋清楚他们之间的关系。
可以用,但别到处都用。
参考
《JavaScript 设计模式与开发实践》