在许多地方都能见到基于发布/订阅模式和观察者模式设计的框架
- 函数或插件在浏览器中使用addEventListener(type,fn)对dom元素进行事件委托。
- 事件监听用户的异步操作Android中也有一个事件发布/订阅的轻量级框架:EventBus,原理与web相似Socket.io的许多方法也是基于此类模式,监听与触发事件,批量广播等。
- 在Node中同样也有一个events事件触发器解决异步操作的同步响应
观察者模式:
Subject(主题,被观察者)
:当状态发送变化时,通知队列中关联对象
Observer(观察者)
:当Subject发送消息时,通过回调获得信息
Observer(观察者)将事件(记做fn回调)丢给Subject(被观察者),开始监视事件,当Subject(被观察者)的(异步)任务完成后,同步触发事件fn被激活,回调将会触发(Fire Event)把消息传输给Observer(观察者)后,完成一个完整的周期
Publisher-Subscriber Pattern(发布者/订阅者模式):
Subscriber(订阅者)
:将事件注册到事件调度中心(Event Channel或者可以看做EventBus(事件总线))
Publisher(发布者)
:触发调度中心的事件
Event Channel(调度中心),与Vue和Android中的EventBus(事件总线)相似:
得到Publisher(发布者)的消息后,统一处理Subscriber(订阅者)注册的事件
Subscriber(订阅者)通过on将事件注册到Event Channel(调度中心),并与Event Channel通过回调进行数据传递,当Subscriber(订阅者)触发Event Channel(调度中心)的事件并将数据传递至其中时,调度中心会激活之前与Subscriber(订阅者)建立的联系,通过emit发送数据,订阅者收到数据后完成一个周期
发布者/订阅者模式实际上是基于观察者模式上优化实现的
二者的区别:
发布订阅模式:订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。
观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
优点:观察者和被观察者是抽象耦合的,其二者建立了一套触发机制,松耦合
缺点:二者之间循环依赖,如果关系复杂,如观察者数量过多,还是会造成性能问题,解决方式是避免同步执行造成线程阻塞
发布者/订阅者模式:与观察者模式类似,但是核心区别是发布者与订阅者互相无耦合,并不知道通知与被通知的对方的具体身份,而是将注册的函数放在统一的调度中心进行管理
优点:发布者/订阅者完全解耦,可扩展性高,常应用在分布式,紧耦合服务中
缺点:发布者解耦订阅者,这点既是主要优点,亦是缺点,打个比方,在Socket中,倘若服务端发送消息给客户端,不会在意是否发送成功,此时需要客户端返回接收到了消息才能算是保证了代码的可靠性和可用性
简易版的消息中心
起步
参考node中的 events事件触发器 我总结归类出了以下函数
on :注册事件
emit:触发事件
un:事件销毁
once:注册事件,执行后即销毁
clear:重置事件列表(消息中心)
has:判断事件是否被订阅
handlerLength:返回某个事件的监听函数数量
watch:与on一样,不同点是可以将结果返回至发布者
invoke:与emit一样,配合watch使用,当watch中存在异步操作时接收其结果
功能设计
了解了实现的功能,我们把类的接口实现一下,其中events是之前文章中的调度中心,使用一个对象来存取所有绑定的事件
1 | export declare interface Handlers { |
工具函数实现
接口完成后,我们来写一些工具函数,这些函数不需要暴露在外面所以在类中,比如异常处理函数:用于解析参数是否异常;单例函数:返回当前类的实例的单例;批量执行函数:执行events中与事件名绑定的函数列表;批量销毁函数:批量销毁调度中心中某个函数集;此外,还有一个函数用于区别watch、invoke和on、emit的事件类型(type)的字符串混入。
异常处理函数:
1 | /** |
单例函数:
1 | //返回当前类的实例的单例 |
批量执行函数:
// 批量执行调度中心中某个函数集
1
2
3
4
5private runHandler(type, data) {
for (let i = 0; i < this.events[type].length; i++) {
this.events[type][i] && this.events[type][i](data)
}
}
批量销毁函数:
1 | // 批量销毁调度中心中某个函数集 |
字符串混入:
1
2
3private prefixStr(str) {
return `@${str}`
}
消息中心类实现
工具函数实现完成后,我们就可以正式开始实现接口中定义的各种函数了,以下是函数的实现过程,其中this.events是事件调度中心,一个以事件type为key的对象
has:
1 | // 判断事件是否被订阅 |
on:
1 | /** |
emit:
1 | /** |
un:
1
2
3
4
5//销毁监听
un(type, handler) {
this.unHandler(type, handler)
return this
}
once:
1 | // 只注册一次监听,执行即销毁 |
clear:
1
2
3
4
5// 重置调度中心
clear() {
this.events = {}
return this
}
handlerLength:
1 | // 一个事件被绑定了多少函数 |
watch:
1
2
3
4
5
6
7
8
9// 监听invoke的消息,若handler中进行了计算或者异步操作,会反馈给invoke
watch(type, handler) {
this.checkHandler(type, handler)
const fn = (...args) => {
this.emit(this.prefixStr(type), handler(...args));
}
this.on(type, fn);
return this
}
invoke:
1
2
3
4
5
6
7 // 触发watch事件,并且接收watch处理结果
invoke(type, data) {
return new Promise<void>((resolve) => {
this.once(this.prefixStr(type), resolve);
this.emit(type, data);
})
}
验证功能
实现完成后,我们试试效果
on=>emit,和之前一样,on监听一个或多个事件,emit触发该事件名下所有事件
1 | function funcA(args) { |
on=>emit=>un=>emit,on监听事件,un销毁事件且不再允许emit当前函数,若不传函数,则清除当前type(事件名)下所有函数
1 | messageCenter.on("a", funcB); |
once=>emit=>emit,once监听一个或多个事件,emit触发事件后立即销毁
1 | messageCenter.once("a", funcB); |
on=>clear=>has,on监听不同的事件,clear重置当前事件列表
1 | messageCenter.on("a", funcB); |
watch=>invoke,watch注册事件,invoke触发事件并等待结果
1 | const funcC = async (args) => { |
写在最后
以上就是文章的所有内容了,如果对源码有兴趣的同学可以进入下面链接或者用npm,pnpm下载
Gitee:MessageCenter: 基于发布订阅模式实现的一个事件消息中心
NPM:event-message-center - npm
源码参考:message-center.js - npm
码云: 观察者模式&发布者订阅者模式