五、实战示例
1. 简单示例
- 定义发布者接口(Publisher)、事件总线接口(EventChannel)和订阅者接口(Subscriber):
interface Publisher<T> { subscriber: string; data: T; } interface EventChannel<T> { on : (subscriber: string, callback: () => void) => void; off : (subscriber: string, callback: () => void) => void; emit: (subscriber: string, data: T) => void; } interface Subscriber { subscriber: string; callback: () => void; } // 方便后面使用 interface PublishData { [key: string]: string; }
- 实现具体发布者类(ConcretePublisher):
class ConcretePublisher<T> implements Publisher<T> { public subscriber: string = ""; public data: T; constructor(subscriber: string, data: T) { this.subscriber = subscriber; this.data = data; } }
- 实现具体事件总线类(ConcreteEventChannel):
class ConcreteEventChannel<T> implements EventChannel<T> { // 初始化订阅者对象 private subjects: { [key: string]: Function[] } = {}; // 实现添加订阅事件 public on(subscriber: string, callback: () => void): void { console.log(`收到订阅信息,订阅事件:${subscriber}`); if (!this.subjects[subscriber]) { this.subjects[subscriber] = []; } this.subjects[subscriber].push(callback); }; // 实现取消订阅事件 public off(subscriber: string, callback: () => void): void { console.log(`收到取消订阅请求,需要取消的订阅事件:${subscriber}`); if (callback === null) { this.subjects[subscriber] = []; } else { const index: number = this.subjects[subscriber].indexOf(callback); ~index && this.subjects[subscriber].splice(index, 1); } }; // 实现发布订阅事件 public emit (subscriber: string, data: T): void { console.log(`收到发布者信息,执行订阅事件:${subscriber}`); this.subjects[subscriber].forEach(item => item(data)); }; }
- 实现具体订阅者类(ConcreteSubscriber):
class ConcreteSubscriber implements Subscriber { public subscriber: string = ""; constructor(subscriber: string, callback: () => void) { this.subscriber = subscriber; this.callback = callback; } public callback(): void { }; }
- 运行示例代码:
interface Publisher<T> { subscriber: string; data: T; } interface EventChannel<T> { on : (subscriber: string, callback: () => void) => void; off : (subscriber: string, callback: () => void) => void; emit: (subscriber: string, data: T) => void; } interface Subscriber { subscriber: string; callback: () => void; } interface PublishData { [key: string]: string; } class ConcreteEventChannel<T> implements EventChannel<T> { // 初始化订阅者对象 private subjects: { [key: string]: Function[] } = {}; // 实现添加订阅事件 public on(subscriber: string, callback: () => void): void { console.log(`收到订阅信息,订阅事件:${subscriber}`); if (!this.subjects[subscriber]) { this.subjects[subscriber] = []; } this.subjects[subscriber].push(callback); }; // 实现取消订阅事件 public off(subscriber: string, callback: () => void): void { console.log(`收到取消订阅请求,需要取消的订阅事件:${subscriber}`); if (callback === null) { this.subjects[subscriber] = []; } else { const index: number = this.subjects[subscriber].indexOf(callback); ~index && this.subjects[subscriber].splice(index, 1); } }; // 实现发布订阅事件 public emit (subscriber: string, data: T): void { console.log(`收到发布者信息,执行订阅事件:${subscriber}`); this.subjects[subscriber].forEach(item => item(data)); }; } class ConcretePublisher<T> implements Publisher<T> { public subscriber: string = ""; public data: T; constructor(subscriber: string, data: T) { this.subscriber = subscriber; this.data = data; } } class ConcreteSubscriber implements Subscriber { public subscriber: string = ""; constructor(subscriber: string, callback: () => void) { this.subscriber = subscriber; this.callback = callback; } public callback(): void { }; } /* 运行示例 */ const pingan8787 = new ConcreteSubscriber( "running", () => { console.log("订阅者 pingan8787 订阅事件成功!执行回调~"); } ); const leo = new ConcreteSubscriber( "swimming", () => { console.log("订阅者 leo 订阅事件成功!执行回调~"); } ); const lisa = new ConcreteSubscriber( "swimming", () => { console.log("订阅者 lisa 订阅事件成功!执行回调~"); } ); const pual = new ConcretePublisher<PublishData>( "swimming", {message: "pual 发布消息~"} ); const eventBus = new ConcreteEventChannel<PublishData>(); eventBus.on(pingan8787.subscriber, pingan8787.callback); eventBus.on(leo.subscriber, leo.callback); eventBus.on(lisa.subscriber, lisa.callback); // 发布者 pual 发布 "swimming"相关的事件 eventBus.emit(pual.subscriber, pual.data); eventBus.off (lisa.subscriber, lisa.callback); eventBus.emit(pual.subscriber, pual.data); /* 输出结果: [LOG]: 收到订阅信息,订阅事件:running [LOG]: 收到订阅信息,订阅事件:swimming [LOG]: 收到订阅信息,订阅事件:swimming [LOG]: 收到发布者信息,执行订阅事件:swimming [LOG]: 订阅者 leo 订阅事件成功!执行回调~ [LOG]: 订阅者 lisa 订阅事件成功!执行回调~ [LOG]: 收到取消订阅请求,需要取消的订阅事件:swimming [LOG]: 收到发布者信息,执行订阅事件:swimming [LOG]: 订阅者 leo 订阅事件成功!执行回调~ */
完整代码如下:
interface Publisher { subscriber: string; data: any; } interface EventChannel { on : (subscriber: string, callback: () => void) => void; off : (subscriber: string, callback: () => void) => void; emit: (subscriber: string, data: any) => void; } interface Subscriber { subscriber: string; callback: () => void; } class ConcreteEventChannel implements EventChannel { // 初始化订阅者对象 private subjects: { [key: string]: Function[] } = {}; // 实现添加订阅事件 public on(subscriber: string, callback: () => void): void { console.log(`收到订阅信息,订阅事件:${subscriber}`); if (!this.subjects[subscriber]) { this.subjects[subscriber] = []; } this.subjects[subscriber].push(callback); }; // 实现取消订阅事件 public off(subscriber: string, callback: () => void): void { console.log(`收到取消订阅请求,需要取消的订阅事件:${subscriber}`); if (callback === null) { this.subjects[subscriber] = []; } else { const index: number = this.subjects[subscriber].indexOf(callback); ~index && this.subjects[subscriber].splice(index, 1); } }; // 实现发布订阅事件 public emit (subscriber: string, data = null): void { console.log(`收到发布者信息,执行订阅事件:${subscriber}`); this.subjects[subscriber].forEach(item => item(data)); }; } class ConcretePublisher implements Publisher { public subscriber: string = ""; public data: any; constructor(subscriber: string, data: any) { this.subscriber = subscriber; this.data = data; } } class ConcreteSubscriber implements Subscriber { public subscriber: string = ""; constructor(subscriber: string, callback: () => void) { this.subscriber = subscriber; this.callback = callback; } public callback(): void { }; } /* 运行示例 */ const pingan8787 = new ConcreteSubscriber( "running", () => { console.log("订阅者 pingan8787 订阅事件成功!执行回调~"); } ); const leo = new ConcreteSubscriber( "swimming", () => { console.log("订阅者 leo 订阅事件成功!执行回调~"); } ); const lisa = new ConcreteSubscriber( "swimming", () => { console.log("订阅者 lisa 订阅事件成功!执行回调~"); } ); const pual = new ConcretePublisher( "swimming", {message: "pual 发布消息~"} ); const eventBus = new ConcreteEventChannel(); eventBus.on(pingan8787.subscriber, pingan8787.callback); eventBus.on(leo.subscriber, leo.callback); eventBus.on(lisa.subscriber, lisa.callback); // 发布者 pual 发布 "swimming"相关的事件 eventBus.emit(pual.subscriber, pual.data); eventBus.off (lisa.subscriber, lisa.callback); eventBus.emit(pual.subscriber, pual.data); /* 输出结果: [LOG]: 收到订阅信息,订阅事件:running [LOG]: 收到订阅信息,订阅事件:swimming [LOG]: 收到订阅信息,订阅事件:swimming [LOG]: 收到发布者信息,执行订阅事件:swimming [LOG]: 订阅者 leo 订阅事件成功!执行回调~ [LOG]: 订阅者 lisa 订阅事件成功!执行回调~ [LOG]: 收到取消订阅请求,需要取消的订阅事件:swimming [LOG]: 收到发布者信息,执行订阅事件:swimming [LOG]: 订阅者 leo 订阅事件成功!执行回调~ */
2. Vue.js 使用示例
参考文章:《Vue事件总线(EventBus)使用详细介绍》 。
2.1 创建 event bus
在 Vue.js 中创建 EventBus 有两种方式:
- 手动实现,导出 Vue 实例化的结果。
// event-bus.js import Vue from 'vue' export const EventBus = new Vue();
- 直接在项目中的
main.js
全局挂载 Vue 实例化的结果。
// main.js Vue.prototype.$EventBus = new Vue()
2.2 发送事件
假设你有两个Vue页面需要通信: A 和 B ,A页面按钮上绑定了点击事件,发送一则消息,通知 B 页面。
<!-- A.vue --> <template> <button @click="sendMsg()">-</button> </template> <script> import { EventBus } from "../event-bus.js"; export default { methods: { sendMsg() { EventBus.$emit("aMsg", '来自A页面的消息'); } } }; </script>
2.3 接收事件
B 页面中接收消息,并展示内容到页面上。
<!-- IncrementCount.vue --> <template> <p>{{msg}}</p> </template> <script> import { EventBus } from "../event-bus.js"; export default { data(){ return { msg: '' } }, mounted() { EventBus.$on("aMsg", (msg) => { // A发送来的消息 this.msg = msg; }); } }; </script>
同理可以从 B 页面往 A 页面发送消息,使用下面方法:
// 发送消息 EventBus.$emit(channel: string, callback(payload1,…)) // 监听接收消息 EventBus.$on(channel: string, callback(payload1,…))
2.4 移除事件监听者
使用 EventBus.$off('aMsg')
来移除应用内所有对此某个事件的监听。或者直接用 EventBus.$off()
来移除所有事件频道,不需要添加任何参数 。
import { eventBus } from './event-bus.js' EventBus.$off('aMsg', {})
六、总结
观察者模式和发布-订阅模式的差别在于事件总线,如果有则是发布-订阅模式,反之为观察者模式。所以在实现发布-订阅模式,关键在于实现这个事件总线,在某个特定时间触发某个特定事件,从而触发监听这个特定事件的组件进行相应操作的功能。发布-订阅模式在很多时候非常有用。
参考文章
1.《发布/订阅》