JavaScript版发布-订阅者模式

本文是基于JavaScript的发布-订阅者模式
内容包括:

  • 什么是发布-订阅模式
  • 如何实现
  • 一个简单的发布-订阅模式
  • Vue中的发布订阅者
  • 发布-订阅者模式和观察者模式的区别

什么是发布-订阅模式?

定义

发布-订阅模式:一种对象间一对多的依赖关系

  • 当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。

订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel)

发布者(Publisher)发布订阅者注册的事件(Publish Event)到调度中心

  • 该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码

常见的发布订阅者模式:Vue的自定义事件、node的事件处理机制

例子

订阅公众号为例,当用户订阅公众号后,每当有文章推送,就会自动发送给用户

典型的发布订阅模式

  • 公众号属于发布者,用户属于订阅者
  • 用户将订阅公众号的事件注册到调度中心
  • 公众号作为发布者,当有新文章发布时,公众号发布该事件到调度中心,调度中心会及时发消息告知用户

如何实现?

基本思想

  • 一个类(EventEmitter)的实例中存储了订阅的函数(callback),调用者通过名字(event)来触发函数

一个简单的发布-订阅模式

EventEmitter

  • 发布-订阅类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class EventEmitter{
constructor(){
// 用于存储订阅事件名以及事件
this.events = {}
}
// 订阅事件
on(event,cb){
/**
* event: 订阅事件名
* fn: 事件
*/
// 若订阅已存在则直接添加,否则创建一个空数组后添加
(this.events[event] || (this.events[event] = [])).push(cb)
}
// 发布事件
emit(event){
// 订阅事件存在才能发布
this.events[event] && this.events[event].forEach(cb=>{
// 遍历并执行该事件绑定的所有事件
cb()
})
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
let publisher = new EventEmitter();  // 声明一个公众号 publisher

// 用户一订阅 news 专栏
publisher.on('news',function(){
console.log("帅哥,您订阅的文章更新啦!");
})
// 用户二订阅 news 专栏
publisher.on('news',function(){
console.log("美女,您订阅的文章更新啦!");
})
// 公众号更新 news专栏
publisher.emit('news')

上面代码输出:

帅哥,您订阅的文章更新啦!
美女,您订阅的文章更新啦!

完善发布订阅者

  • 可以只订阅一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class EventEmitter{
constructor(){
// 用于存储订阅事件名以及事件
this.events = {}
}
// 订阅事件
on(event,cb){
/**
* event: 订阅事件名
* fn: 事件
*/
// 若订阅已存在则直接添加,否则创建一个空数组后添加
(this.events[event] || (this.events[event] = [])).push(cb)
}
// 发布事件
emit(event){
// 订阅事件存在才能发布
this.events[event] && this.events[event].forEach(cb=>{
// 遍历并执行该事件绑定的所有事件
cb()
})
}
// 订阅事件,只执行一次
once(event,cb){
let fn = ()=>{
cb()
this.removeListener(event,fn)
}
on(event,fn)
}
// 移除订阅事件
removeListener(event,cb){
this.events[event] && this.events[event].fillter(fn=>fn!=cb)
}
}

Vue中的发布订阅者

在Vue中Vue实例既是发布者,也是订阅者,又是消息中心。

使用

1.Vue自定义事件

1
2
3
4
5
6
7
8
9
10
11
12
13
let vm = new Vue()

// 订阅事件
vm.$on('dataChange',
() => {
console.log('dataChange1')
})
vm.$on('dataChange',
() => {
console.log('dataChange2')
})
// 发布事件
vm.$emit('dataChange')

输出
dataChange1
dataChange2

2.可以用于兄弟组件间通信

  • vue 2写法
  • 注意:Vue 3.x中$on$off$once 实例方法已被移除,组件实例不再实现事件触发接口。

创建一个空的Vue实例作为消息中心,进行订阅和发布

  • 通过这个实例使得不同组件间可以进行通信

PS:以下三个文件都放在同一个文件夹下

eventBus.js

1
2
import Vue from 'Vue'
export default new Vue;

comA.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div>
<button @click="publish('hello')">
发布
</button>
</div>
</template>
<script>
import bus from './eventBus.js'
export default {
motheds:{
publish(event){
bus.$emit(event,"hello world")
}
}
}
</script>

comB.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>
订阅者: {{msg}}
</div>
</template>
<script>
import bus from './eventBus.js'
export default {
data(){
return {
msg:''
}
},
mounted(){
let _this = this
bus.$on('hello',function(data){
_this.msg = data
})
}
}
</script>

发布-订阅者模式和观察者模式的区别

观察者模式是由具体目标调度

  • 比如当事件触发,Dep 就会去调用观察者的方法,所以观察者模式的订阅者与发布者之间是存在依赖的。

发布/订阅模式由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在

  • 事件中心隔离了发布者与订阅者,减少了互相依赖的关系。

image-20211117150523175

参考

参考

参考