实现一个简单的发布订阅模式
一、发布订阅模式是什么?
发布订阅模式是对象间的一种一对多的关系,由发布者、订阅者、消息管理器三部分组成,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知。
不论是在程序世界里还是现实生活中,发布—订阅模式的应用都非常广泛。我们先看一下现实中的例子: 小明最近看上了一套房子,到了售楼处之后才被告知,该楼盘的房子早已售罄。好在售楼MM告诉小明,不久后还有一些尾盘推出。开发商正在办理相关手续,手续办好后便可以购买。但到底是什么时候,目前还没有人能够知道。 于是小明记下了售楼处的电话,以后每天都会打电话过去询问是不是已经到了购买时间。除了小明,还有小红,小强,小龙也会每天向售楼处咨询这个问题。一个星期过后,售楼MM决定辞职,因为厌倦了每天回答1000个相同的电话。 当然现实中没有这么笨的销售公司,实际上故事是这样的:小明离开之前,把电话号码留在售楼处。售楼MM答应他,新楼盘一推出就马上发信息通知小明。小红、小强、小龙也是一样,他们的电话号码都被记在售楼处的花名册上,新楼盘推出的时候,售楼MM会翻开花名册,遍历上面的电话号码,依次发送一条短信通知他们。
二、发布订阅模式的优缺点
优点:
缺点:
-
创建订阅者需要消耗一定的时间和内存 虽然可以弱化对象之间的联系,但是如果过度使用的话,代码反而不太好理解和维护。特别是有多个发布者和订阅者的嵌套到一起的时候,要跟踪一个bug并不是一件轻松的事情
三、实现一个通用的发布订阅模式
// 定义事件中心类
class MyEvent {
handlers = {
} // 存放事件 map,发布者,存放订阅者
// 绑定事件
$on(type, fn) {
if (!Reflect.has(this.handlers, type)) {
// 如果没有定义过该事件,初始化该订阅者列表
this.handlers[type] = []
}
this.handlers[type].push(fn) // 存放订阅的消息
}
// 触发事件
$emit(type, ...params) {
if (!Reflect.has(this.handlers, type)) {
// 如果没有该事件,抛出错误
throw new Error(`未注册该事件${
type}`)
}
this.handlers[type].forEach((fn) => {
// 循环事件列表,执行每一个事件,相当于向订阅者发送消息
fn(...params)
})
}
// 取消订阅
$remove(type, fn) {
if (!Reflect.has(this.handlers, type)) {
throw new Error(`无效事件${
type}`)
}
if (!fn) {
// 如果没有传入方法,表示需要将该该类型的所有消息取消订阅
return Reflect.deleteProperty(this.handlers, type)
} else {
const inx = this.handlers[type].findIndex((handler) => handler === fn)
if (inx === -1) {
// 如果该事件不在事件列表中,则抛出错误
throw new Error(无效事件)
}
this.handlers[type].splice(inx, 1) // 从事件列表中删除该事件
if (!this.handlers[type].length) {
// 如果该类事件列表中没有事件了,则删除该类事件
return Reflect.deleteProperty(this.handlers, type)
}
}
}
}
