知食记
搜索文档…
发布-订阅模式

练习-实现一个Event Bus/ Event Emitter

Event Bus(Vue、Flutter 等前端框架中有出镜)和 Event Emitter(Node中有出镜)出场的“剧组”不同,但是它们都对应一个共同的角色——全局事件总线。 全局事件总线,严格来说不能说是观察者模式,而是发布-订阅模式(具体的概念甄别我们会在下个小节着重讲)。它在我们日常的业务开发中应用非常广。
上节开篇我说过,如果只能考一个设计模式的面试题,我一定会出观察者模式。
这句话接着往下说,如果只能选一道题,那这道题一定是 Event Bus/Event Emitter 的代码实现——我都说这么清楚了,这个知识点到底要不要掌握、需要掌握到什么程度,就看各位自己的了。

在Vue中使用Event Bus来实现组件间的通讯

Event Bus/Event Emitter 作为全局事件总线,它起到的是一个沟通桥梁的作用。我们可以把它理解为一个事件中心,我们所有事件的订阅/发布都不能由订阅方和发布方“私下沟通”,必须要委托这个事件中心帮我们实现。 在Vue中,有时候 A 组件和 B 组件中间隔了很远,看似没什么关系,但我们希望它们之间能够通信。这种情况下除了求助于 Vuex 之外,我们还可以通过 Event Bus 来实现我们的需求。
创建一个 Event Bus(本质上也是 Vue 实例)并导出:
1
const EventBus = new Vue()
2
export default EventBus
Copied!
在主文件里引入EventBus,并挂载到全局:
1
import bus from 'EventBus的文件路径'
2
Vue.prototype.bus = bus
Copied!
订阅事件:
1
// 这里func指someEvent这个事件的监听函数
2
this.bus.$on('someEvent', func)
Copied!
发布(触发)事件:
1
// 这里params指someEvent这个事件被触发时回调函数接收的入参
2
this.bus.$emit('someEvent', params)
Copied!
大家会发现,整个调用过程中,没有出现具体的发布者和订阅者(比如上节的PrdPublisher和DeveloperObserver),全程只有bus这个东西一个人在疯狂刷存在感。这就是全局事件总线的特点——所有事件的发布/订阅操作,必须经由事件中心,禁止一切“私下交易”!
下面,我们就一起来实现一个Event Bus(注意看注释里的解析):
1
class EventEmitter {
2
constructor() {
3
// handlers是一个map,用于存储事件与回调之间的对应关系
4
this.handlers = {}
5
}
6
7
// on方法用于安装事件监听器,它接受目标事件名和回调函数作为参数
8
on(eventName, cb) {
9
// 先检查一下目标事件名有没有对应的监听函数队列
10
if (!this.handlers[eventName]) {
11
// 如果没有,那么首先初始化一个监听函数队列
12
this.handlers[eventName] = []
13
}
14
15
// 把回调函数推入目标事件的监听函数队列里去
16
this.handlers[eventName].push(cb)
17
}
18
19
// emit方法用于触发目标事件,它接受事件名和监听函数入参作为参数
20
emit(eventName, ...args) {
21
// 检查目标事件是否有监听函数队列
22
if (this.handlers[eventName]) {
23
// 如果有,则逐个调用队列里的回调函数
24
this.handlers[eventName].forEach((callback) => {
25
callback(...args)
26
})
27
}
28
}
29
30
// 移除某个事件回调队列里的指定回调函数
31
off(eventName, cb) {
32
const callbacks = this.handlers[eventName]
33
const index = callbacks.indexOf(cb)
34
if (index !== -1) {
35
callbacks.splice(index, 1)
36
}
37
}
38
39
// 为事件注册单次监听器
40
once(eventName, cb) {
41
// 对回调函数进行包装,使其执行完毕自动被移除
42
const wrapper = (...args) => {
43
cb.apply(...args)
44
this.off(eventName, wrapper)
45
}
46
this.on(eventName, wrapper)
47
}
48
}
Copied!
最近更新 1yr ago
复制链接