前言
首先了解发布-订阅模式之前,可以先了解一下观察者模式,观察者模式比较简单,而发布-订阅模式是在观察者模式的基础上做了一个升级,观察者模式
正文
发布-订阅模式的初步了解
1.发布-订阅模式VS观察者模式
观察者模式
发布-订阅模式
两个模式都是实现对象之间的通信问题,这里的例子是房东和租客之间的关系,同样的需求,但两者的实现是存在极大差别的,这里的观察者/订阅者们的需求是什么,都是租房子,但两个模式的差别是什么,图片中很明显的可以看出:
第一个观察者模式,所有的租客想看更多的房子就需要找不同的房东,之间需要建立联系,他们之间的关系线也就相当复杂。
第二个发布-订阅模式,所有的租客只需要联系中介,就可以看四套房子,就不需要一一去找对应的房东。
这也就是为什么观察者模式称为一对多通信,因为他是从被观察者的角度出发,一个被观察者需要与多个观察者建立联系,而发布-订阅模式称为多对多通信,只需要通过一个中间层,就可以完成多对多之间的联系。
解决:都是解决通信问题
区别:比观察者模式多了一个中间层
好处:加中间层的好处,解耦、扩展性、灵活
2.发布-订阅模式的组成
发布-订阅模式是由发布者、订阅者、事件中心三个角色组成的一个模式
左边是具体关系, 右边是上面例子的关系,相结合更容易理解
用例子简单讲解一下,就是房东只需要把房子放到中介处,然后当租客想租房的时候,只要找中介,告诉中介想看哪些房子,中介就会找到对应的房东,拿到房子信息,并且通知租客。
发布-订阅模式的详细了解
1.角色的任务
事件总线
发布-订阅模式的任务首先收集订阅者提供的信息,也就是需要一个存放数据的容器,然后接收发布者发布的更新,查照是否存在对应的数据,最后进行通知订阅者。
订阅者
订阅者需要做什么? 一个是订阅事件,一个是触发事件,首先要订阅,肯定是有需求才会加入到这个行列中来的,那订阅者要做的就是订阅事件,把需求告诉事件中心,然后让事件中心去处理,同时也把处理方式告诉事件中心, 等到发布者的响应后,接收事件中心的通知。
发布者
发布者的需求很简单,就是再数据发生变化时,告诉事件中心,我某个数据发生了某种变化,其余的事情都交给事件中心处理。
2.实战示例
这里使用js来进行示例演示,下面是事件总线类,先过一遍代码
class EventEmitter {
constructor() {
this._events = {};
}
on(eventName, callback) {
if (!this._events[eventName]) {
this._events[eventName] = [callback];
return;
}
this._events[eventName].push(callback);
}
emit(eventName, datas) {
if(!this._events[eventName]) return;
this._events[eventName].forEach(cb => cb(datas))
}
off(eventName, callback) {
if (!callback) this._events[eventName] = undefined
if (!this._events[eventName]) return;
this._events[eventName] = this._events[eventName].filter(item => { return item != callback; })
}
}
来看看事件总线具体做了哪些事
解析:
- 这里创建了一个
EventEmitter类, - 构造函数定义了一个
_events对象,也就是用来存放订阅者的容器。 了解一下_events的存放格式
_events = {
事件名称1 : [回调函数1,回调函数2,...],
事件名称2 : [回调函数1,回调函数2,...],
}
//事件名称可以看作是和发布者的绑定,
//回调函数就是与该发布者有关的订阅者,
//这样看是不是和观察者模式就一样了。
//区别:观察者模式是把观察者存放在被观察者中的数组中,
//而发布-订阅模式是将发布者和订阅者以键值对的形式存放再事件总线中
on()具体实现:首先有两个参数(事件名称,回调函数),事件名称可以看作是寻找某个发布者,而回调函数就是订阅者本身,进行判断,判断数组中是否存在该事件名称,不存在就创建一个,并把callback存入,如果存在直接将callback存入,这个过程就是订阅事件,也就是建立与发布者的联系。emit()具体实现:也是有两个参数(事件名称,传回的数据),在this._events中查找对应事件名称的value值,也就是一个存放callback的数组,遍历数组,执行其中的回调函数。off()具体实现:一个类存在绑定,完整一点肯定也需要一个解绑,释放存储空间,
这就是事件总线中完成的事情
on方法的调用
const events = new EventEmitter()
events.on("newListener", function(eventName) {
console.log(`eventName`, eventName)
})
events.on("hello", function() {
console.log("hello");
})
let cb = function() {
console.log('cb');
}
events.on("hello", cb)
events.emit("hello") // hello cb
解析:
- 首先创建一个EventEmitter实例
- 调用on方法,传入事件名称和回调函数
- 总共调用了三次on方法
- 调用emit方法,进行执行某个时间名下的回调函数
off方法的调用
还是根据上面那个例子,我们来看看off方法的使用,也就是删除的使用。
//第一种使用
events.off("hello", cb) //这里把hello事件名称下的cb回调删除了,这样调用emit就不会执行cb的回调函数了
//再次打印
events.emit("hello") // hello
//第二种使用
events.off("hello") //不带回调函数名称,就直接把全部回调函数都删除
//再次打印
events.emit("hello") //
emit方法的使用
//修改一下其中一个回调函数
events.on("hello", function(datas) {
console.log("这是带data的回调" + datas);
})
//使用emit时带上第二个参数
events.emit("hello", "数据更新") //这是带data的回调数据更新
//这样就可以获取到返回回来的数据
可以稍微完善一下回调函数,做一个参数校验,简单写一下,根据直接的想法进行实现
events.on("hello", function(datas) {
if(!datas) return console.log("没有返回数据")
console.log("这是带data的回调" + datas);
})
总结
最后再来总结一下,发布-订阅模式是观察者模式的升级版,在需要通信的双方之间添加了一个中间层,也就是事件总线,时间总线把原本属于通信双方需要做的事情,收入到自己这边,由自己来完成,双方只需要调用其中的方法,并且把数据传入就完成任务,减轻了通信双方的耦合性,变得更加灵活。如果觉得总结看不懂的话,建议从头再看一遍~