本博客 hjy-xh,转载请申明出处
介绍
事件总线(EventBus) 是一种消息传递的方式
它可以让两个没有关联的组件进行通信,起到数据传输的作用
举个例子,当module N
发布了Event 1
消息,订阅该消息的module 1
就会收到相关消息(过程如下所示)

场景举例
现在有一所学校,学校里面有一些事件发生,学生能够根据事件类型做出相应的动作
用代码表述
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
| let EB = new EventBus();
EB.$on('上课', (name) => { console.info(`学生${name}到上课地点`); }) EB.$on("教室上课", (name, course) => { console.info(`学生${name}去教室上${course}课`); }) let id = EB.$on("户外上课", (name, course) => { console.info(`学生${name}去户外上${course}课`); }) EB.$once("献血", (name) => { console.info(`学生${name}去献血`); })
EB.$emit('上课', '张三'); EB.$emit('上课', '李四'); EB.$emit('教室上课', '张三', '编程'); EB.$emit('户外上课', '张三', '田径'); EB.$off('户外上课', id); EB.$emit('户外上课', '李四', '足球'); EB.$emit('献血', '王五'); EB.$emit('献血', '马六');
EB.$clear()
|
从上面的代码可以大概了解到,事件总线应该具备五种方法,对应着下一部分的API设计
API设计
- 发布消息(emit)
- 订阅消息(on)
- 能够知道具体的消息类型,并执行回调,回调的参数就是发布消息时携带的参数
- 取消订阅(off)
- 能够知道具体的消息类型,取消订阅该消息类型,即回调函数不再执行
- 仅订阅一次消息(once)
- 能够知道具体的消息类型,执行一次回调后,再次接收相同消息,不再执行回调
- 清除某个或所有事件(clear)
- 如果指定了某个消息类型,则清除该消息类型的回调,否则全部清除
代码实现
基础实现(仅包含订阅和发布)
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 36 37 38
| class EventBus { constructor() { this.eventObj = {}; }
$on(name, callback) { if (!this.eventObj[name]) { this.eventObj[name] = []; } this.eventObj[name].push(callback); }
$emit(name) { const eventList = this.eventObj[name]; for (const callback of eventList) { callback(arguments); } } }
let EB = new EventBus();
EB.$on('上课', () => { console.info("该上课了"); }) EB.$on("上课", () => { console.info("做笔记"); }) EB.$on("下课", () => { console.info("下课啦!"); })
EB.$emit('上课'); EB.$emit('下课');
|
如何在发布消息时携带参数
1 2 3 4 5 6 7 8 9 10 11 12 13
| $on(name, callback) { if (!this.eventObj[name]) { this.eventObj[name] = []; } this.eventObj[name].push(callback); }
$emit(name, ...args) { const eventList = this.eventObj[name]; for (const callback of eventList) { callback(...args); } }
|
一次订阅
1 2 3 4 5 6 7 8 9
| this.onceObj = {};
$once(eventName, callback) { if (!this.onceObj[eventName]) { this.onceObj[eventName] = [] } this.onceObj[eventName].push(callback) }
|
取消订阅
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
$off(eventName, id) { if (id) { delete this.eventObj[eventName][id]; if (!Object.keys(this.eventObj[eventName]).length) { delete this.eventObj[eventName]; } } else { delete this.eventObj[eventName]; } }
|
清除事件
1 2 3 4 5 6 7 8 9
| $clear(eventName: string = '') { if (!eventName) { this.eventObj = {}; return; } delete this.eventObj[eventName]; }
|
终版(TS版本)
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
| interface EventObj { [key: string]: { [id: number]: Function } }
interface OnceEventObj { [key: string]: Function[] }
class EventBus { eventObj: EventObj callbcakId: number onceObj: OnceEventObj
constructor() { this.eventObj = {}; this.callbcakId = 0; this.onceObj = {}; }
$on(eventName: string, callback: Function) { if (!this.eventObj[eventName]) { this.eventObj[eventName] = {}; } const id = this.callbcakId++; this.eventObj[eventName][id] = callback; return id; }
$emit(eventName: string, ...args: any) { const eventList = this.eventObj[eventName]; for (const id in eventList) { typeof eventList[id] === 'function' && eventList[id](...args); } const onceEvent = this.onceObj[eventName]; if (onceEvent) { onceEvent.forEach((callback) => callback(...args)); delete this.onceObj[eventName]; }
}
$off(eventName: string, id: number) { if (id) { delete this.eventObj[eventName][id]; if (!Object.keys(this.eventObj[eventName]).length) { delete this.eventObj[eventName]; } } else { delete this.eventObj[eventName]; } }
$once(eventName: string, callback: Function) { if (!this.onceObj[eventName]) { this.onceObj[eventName] = []; } this.onceObj[eventName].push(callback); }
$clear(eventName: string = '') { if (!eventName) { this.eventObj = {}; return; } delete this.eventObj[eventName]; } }
let EB = new EventBus();
EB.$on('上课', (name: string) => { console.info(`学生${name}到上课地点`); }) EB.$on("教室上课", (name: string, course: string) => { console.info(`学生${name}去教室上${course}课`); }) let id = EB.$on("户外上课", (name: string, course: string) => { console.info(`学生${name}去户外上${course}课`); }) EB.$once("献血", (name: string) => { console.info(`学生${name}去献血`); })
EB.$emit('上课', '张三'); EB.$emit('上课', '李四'); EB.$emit('教室上课', '张三', '编程'); EB.$emit('户外上课', '张三', '田径'); EB.$off('户外上课', id); EB.$emit('户外上课', '李四', '足球'); EB.$emit('献血', '王五'); EB.$emit('献血', '马六'); console.log(EB) EB.$clear() console.log(EB)
|
为什么不被推荐使用
事件总线使用起来非常简单,但是!
- 随着事件的推移,注册的事件可能越来越多,如果没有及时清理相关事件,整个对象占用的内存会越来越大
- 当逻辑变得复杂时大量使用事件总线,会让数据流混乱,难以预测,这样在调试代码时难以定位或修改
替代方式
- 状态提升
- 有时我们需要在兄弟组件间传递数据,这种情况可以把共享状态提升到最近的共同父组件中去
- 使用状态管理工具
- 主流前端开发框架都有相应的状态管理方案,比如redux、mobx、Vuex