发布、订阅,从名称上能够联想到文章的发布和订阅。比如公众号运营者在公众平台上发布一篇文章,阅读者订阅了这个公众号,能够获得这篇文章的推送。发布订阅模式是一种一对多的关系,比如“公众号”和“订阅者”,一个公众号对应很多个订阅者。当公众号文章发布的时候,多个订阅者会收到消息。发布订阅模式和观察者模式非常相像,很多人认为这两种模式是一样的,如果不考虑调度中心,这两种模式没啥差别,如果把调度中心考虑进去,那还是有点区别的。可以换句话说,观察者模式是没有解耦的发布订阅模式。
观察者模式
栗子:A(Observer)做建材批发生意,B(Subject)是建立合作伙伴需要买进建材售卖的商铺。有一天 A 批发市场没进到货,这时他可能就打电话给会前来买货的商铺告诉他们今天没有货,改天再来取货。
这时候 A(Observer)与 B(Subject)直接建立联系。
class StateTracker {
constructor() {
this.observers = []; // 观察者列表
this.internalState = 10;
}
//改变内部状态,触发状态的观察者列表
change(val) {
this.internalState = val;
this.observers.forEach(observer => observer(val));
}
//注册观察者
registerObserver(ObserverFn) {
this.observers.push(ObserverFn);
}
}
let obj = new StateTracker();
let fn = arg => {
// ......
console.log("fn:" + arg);
};
let fn2 = arg => {
// ......
console.log("fn2:" + arg);
};
obj.registerObserver(fn); // 注册
obj.registerObserver(fn2);
console.log(obj.observers); // 注册后的列表[ [Function: fn], [Function: fn2] ]
console.log("1:" + obj.internalState); // 注册后的状态值 10
obj.change(5); // 改变状态值并触发观察者列表 fn:5 fn2:5
console.log("2:" + obj.internalState); // 改变后的状态值 5
观察者模式在 vue 中,vuex
注册Vue.use(Vuex)
,监听数据变化this.$store.commit()
,当有数据更新的时候触发界面数据改变。
发布订阅模式
class PubSubHandler {
constructor() {
this.eventPool = {};
}
//移除
off(topicName) {
delete this.observers[topicName];
}
//发布
trigger(topicName, ...args) {
this.eventPool[topicName] &&
this.eventPool[topicName].forEach(callback => callback(...args));
}
//订阅
on(topicName, callback) {
let topic = this.eventPool[topicName];
if (!topic) {
this.eventPool[topicName] = [];
}
this.eventPool[topicName].push(callback);
}
}
class PublisherReader extends PubSubHandler {
constructor() {
super();
}
// 发布消息
pushMess(name, ...arg) {
this.trigger(name, ...arg);
}
delete() {
this.off(name);
}
getMess(name, callback) {
this.on(name, callback);
}
}
// 例 //
将发布和订阅消息的事件挂载在document节点中,这时DOM节点相当于是调度中心。当需要发布消息的时候向调度中心发布,需要订阅的时候从调度中心获取。
<input type="text" id="getTopicName" placeholder="主题" />
<button id="getMess">订阅</button>
<div id="messContent"></div>
<input type="text" id="content" placeholder="内容" />
<button id="pushMess">发布</button>
....
<script>
...
var topicName = document.getElementById('getTopicName').value
var content = document.getElementById('content').value
let publish = new PublisherReader();
function getMessFun(){
publish.getMess(topicName, (...arg) => { //触发订阅
// ....
var divContent='<ul>';
for(var i=0,len=arg.length;i<len;i++){
divContent += '<li>'+arg[i]+'</li>'
}
divContent += '</ul>'
document.getElementById('messContent').innerHtml = divContent
// ....
}
function pushMessFun(){
publish.pushMess(topicName, content); // 触发发布
}
document.getElementById('getMess').addEventListener('click',getMessFun())
document.getElementById('pushMess').addEventListener('click',pushMessFun())
</script>