发布-订阅模式里面包含了三个模块,发布者,订阅者和处理中心,如下图结构。
1、在实际开发中,经常会遇到某个方法内处理很多的逻辑,最简单的就是直接在方法内直接写。这种是高度耦合的面向过程的写法。对于代码维护不友好。而发布-订阅模式就是将两者分离。其实就是将发布者和订阅者解耦了,我触发了某个事件(这里我们将触发该方法定义为事件),我只向调度中心通知,我并不知道调度中心内会怎么处理,有多少个人响应。我只管通知。而订阅者只管在调度中心订阅,有人调用它才响应。
2、假设我们有3个js文件,事件触发在a.js内,而响应该事件的在b.js和c.js内,要是用常规调用的方法的话,就要把b.js和c.js的方法传到a.js内。这是一个非常麻烦的操作。而发布-订阅模式是将调度中心挂在了全局,我们只管调用调度中心相应的方法注册和订阅。
3、代码实现
<script>
class Event {
constructor () {}
// 首先定义一个事件容器,用来装事件数组(因为订阅者可以是多个)
handlers = {}
// 事件添加方法,参数有事件名和事件方法 =》订阅
addEventListener (type, handler) {
// 首先判断handlers内有没有type事件容器,没有则创建一个新数组容器
if (!(type in this.handlers)) {
this.handlers[type] = []
}
// 将事件存入
this.handlers[type].push(handler)
}
// 触发事件两个参数(事件名,参数)=》发布
dispatchEvent (type, ...params) {
// 若没有注册该事件则抛出错误
if (!(type in this.handlers)) {
return new Error('未注册该事件')
}
// 事件触发
this.handlers[type].forEach(handler => {
handler(...params)
})
}
// 事件移除参数(事件名,删除的事件,若无第二个参数则删除该事件的订阅和发布)
removeEventListener (type, handler) {
// 无效事件抛出
if (!(type in this.handlers)) {
return new Error('无效事件')
}
if (!handler) {
// 直接移除事件
delete this.handlers[type]
} else {
const idx = this.handlers[type].findIndex(ele => ele === handler)
// 抛出异常事件
if (idx === undefined) {
return new Error('无该绑定事件')
}
// 移除事件
this.handlers[type].splice(idx, 1)
if (this.handlers[type].length === 0) {
delete this.handlers[type]
}
}
}
}
</script>
下面实现一个调用的demo
<script>
var event = new Event() // 创建event实例
// 定义一个自定义事件:"load"
function load (params) {
console.log('load', params)
}
event.addEventListener('load', load)
// 再定义一个load事件
function load2 (params) {
console.log('load2', params)
}
//添加到订阅
event.addEventListener('load', load2)
// 触发该事件
event.dispatchEvent('load', 'load事件触发')
// 移除load2事件 移除订阅
event.removeEventListener('load', load2)
// 移除所有load事件
event.removeEventListener('load')
</script>
什么情况下适合使用订阅发布模式:
首先,如果一个数据或者事件的变化会对许多事件产生影响。例如:我们通过websocket从后台实时获取数据,当返回的数据为 load3 时,需要执行关闭数据流,更改数据显示,更新设备状态等等,在不使用订阅开发者模式时,当接收到 load3 后,我们注册一个函数,需要在该函数中写n个函数的执行,但是使用设计者模式,只需要注册一个 load3 的发布函数,其他函数订阅该函数,当发布函数执行后会发布信息,其订阅者会自动更新状态。