发布订阅模式是一种设计模式,它是一种解耦合的方式,用于处理对象之间的通信。在这种模式中,一个对象(发布者)将事件发送到一个中心点,然后订阅者从中心点订阅它们感兴趣的事件,以便在事件发生时得到通知。
在发布订阅模式中,发布者和订阅者是完全解耦合的。发布者不需要知道哪些订阅者存在,而订阅者也不需要知道哪些发布者存在。这种模式提供了一种非常灵活和可扩展的方式来组织代码,并且可以非常容易地添加新的发布者和订阅者。
在 JavaScript 中,常见的应用场景包括事件驱动编程、异步编程、组件通信等。比如在浏览器中,可以通过 addEventListener() 方法订阅一个事件,然后在事件触发时得到通知。在 Node.js 中,可以使用 EventEmitter 类来实现发布订阅模式。
TypeScript 实现
type Subscriber<T> = (data: T) => void;
interface Subscription<T> {
subscriber: Subscriber<T>;
once: boolean;
}
class EventEmitter<T> {
private subscribers: Record<string, Subscription<T>[]> = {};
public on(event: string, subscriber: Subscriber<T>, once = false): void {
if (!this.subscribers[event]) {
this.subscribers[event] = [];
}
this.subscribers[event].push({ subscriber, once });
}
public once(event: string, subscriber: Subscriber<T>): void {
this.on(event, subscriber, true);
}
public off(event: string, subscriber?: Subscriber<T>): void {
if (!this.subscribers[event]) {
return;
}
if (!subscriber) {
delete this.subscribers[event];
return;
}
this.subscribers[event] = this.subscribers[event].filter(
({ subscriber: s }) => s !== subscriber
);
}
public emit(event: string, data?: T): void {
if (!this.subscribers[event]) {
return;
}
this.subscribers[event].forEach(({ subscriber, once }) => {
subscriber(data);
if (once) {
this.off(event, subscriber);
}
});
}
}
这个实现定义了 EventEmitter 类,它有 on、once、off 和 emit 四个方法,分别用于订阅事件、订阅一次性事件、取消订阅事件和触发事件。其中:
on(event, subscriber, once)方法用于订阅事件。event参数是事件名称,subscriber参数是订阅者回调函数,once参数表示是否订阅一次性事件。如果该事件不存在订阅者列表中,则创建一个新的订阅者列表,并添加一个新的订阅者到列表中。once(event, subscriber)方法用于订阅一次性事件。它是on方法的简化版,只订阅一次性事件,等价于on(event, subscriber, true)。off(event, subscriber)方法用于取消订阅事件。如果subscriber参数未指定,则删除该事件的所有订阅者;否则,删除指定订阅者。emit(event, data)方法用于触发事件。它会遍历该事件的订阅者列表,并依次调用订阅者回调函数。如果该事件是一次性事件,则在调用完订阅者回调函数后,删除该订阅者。
使用该实现可以创建一个事件发射器对象,并订阅和触发事件。例如:
const emitter = new EventEmitter<number>();
emitter.on('increment', (count) => {
console.log(`Count is ${count}`);
});
emitter.emit('increment', 1); // Count is 1
emitter.emit('increment', 2); // Count is 2
该示例创建了一个 EventEmitter 对象 emitter,并订阅了 increment 事件。每次调用 emit 方法时,increment 事件的订阅者会被依次调用。
JavaScript 实现
class EventEmitter {
constructor() {
this.events = {};
}
on(eventName, listener) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(listener);
}
emit(eventName, ...args) {
if (this.events[eventName]) {
this.events[eventName].forEach((listener) => listener(...args));
}
}
off(eventName, listener) {
if (!this.events[eventName]) {
return;
}
const index = this.events[eventName].indexOf(listener);
if (index !== -1) {
this.events[eventName].splice(index, 1);
}
}
once(eventName, listener) {
const onceWrapper = (...args) => {
listener.apply(this, args);
this.off(eventName, onceWrapper);
};
this.on(eventName, onceWrapper);
}
}