Original text: dushusir.com/js-event-bu…
introduce
An Event Bus is usually used as a communication mechanism between multiple modules. It functions as an Event management center. One module sends messages and the other modules receive messages.
For example, data passing between Vue components can be communicated using an Event Bus, or it can be used as plug-in and core communication in a microkernel plug-in system.
The principle of
Event Bus essentially adopts the publish-subscribe design mode. For example, multiple modules A, B, and C subscribe to an EventX, and then one module X publishes the Event in the Event Bus. Then the Event Bus is responsible for informing all subscribers A, B, and C that they can receive the notification message. You can also pass parameters.
/ / diagramModule X ⬇ release EventX ╔ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ╗ ║ Event Bus ║ ║ ║ ║ EventX 】 【 【EventY】 【EventZ】... ║ ╚ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ╝ subscription EventX ⬆ ⬆ subscription EventX ⬆ subscription EventX module module B module CCopy the code
Analysis of the
How do I implement a simple version of the Event Bus using JavaScript
- First of all, construct a
EventBus
Class that initializes an empty object to hold all events - The event name is used as the key value when the subscription is accepted, and the callback function that needs to be executed after the publication message is accepted is used as the value. Since an event can have multiple subscribers, the callback function here is stored in a list
- When an event message is released, all the callback functions corresponding to the specified event name are obtained from the event list and triggered in sequence
The following is a detailed implementation of the code, which can be copied to the Google Browser console directly run the detection effect.
code
class EventBus {
constructor() {
// Initializes the event list
this.eventObject = {};
}
// Publish events
publish(eventName) {
// Retrieve all callback functions for the current event
const callbackList = this.eventObject[eventName];
if(! callbackList)return console.warn(eventName + " not found!");
// Execute each callback function
for (let callback ofcallbackList) { callback(); }}// Subscribe to events
subscribe(eventName, callback) {
// Initialize the event
if (!this.eventObject[eventName]) {
this.eventObject[eventName] = [];
}
// Store the subscriber callback function
this.eventObject[eventName].push(callback); }}/ / test
const eventBus = new EventBus();
// Subscribe to eventX
eventBus.subscribe("eventX".() = > {
console.log(Module "A");
});
eventBus.subscribe("eventX".() = > {
console.log("Module B");
});
eventBus.subscribe("eventX".() = > {
console.log(Module "C");
});
// Publish event eventX
eventBus.publish("eventX");
/ / output> Module A > Module B > Module CCopy the code
Above we have implemented the basic publish and subscribe functionality, but in practice there may be more advanced requirements.
The advanced
1. How do I pass parameters when sending messages
The publisher passes in a parameter to the EventBus, which is then passed out when the callback function is executed so that each subscriber receives the parameter.
code
class EventBus {
constructor() {
// Initializes the event list
this.eventObject = {};
}
// Publish events
publish(eventName, ... args) {
// Retrieve all callback functions for the current event
const callbackList = this.eventObject[eventName];
if(! callbackList)return console.warn(eventName + " not found!");
// Execute each callback function
for (let callback of callbackList) {
// Pass in the arguments when executing
callback(...args);
}
}
// Subscribe to events
subscribe(eventName, callback) {
// Initialize the event
if (!this.eventObject[eventName]) {
this.eventObject[eventName] = [];
}
// Store the subscriber callback function
this.eventObject[eventName].push(callback); }}/ / test
const eventBus = new EventBus();
// Subscribe to eventX
eventBus.subscribe("eventX".(obj, num) = > {
console.log(Module "A", obj, num);
});
eventBus.subscribe("eventX".(obj, num) = > {
console.log("Module B", obj, num);
});
eventBus.subscribe("eventX".(obj, num) = > {
console.log(Module "C", obj, num);
});
// Publish event eventX
eventBus.publish("eventX", { msg: "EventX published!" }, 1);
/ / output> module A {msg: 'EventX published! '} 1> Module B {msg: 'EventX published! '} 1> module C {msg: 'EventX published! '} 1
Copy the code
2. How do I unsubscribe after I subscribe
Sometimes subscribers only want to subscribe to messages for a certain period of time, which involves having the ability to unsubscribe. We will modify the code.
First, we implement the specified subscriber to unsubscribe, each subscription event, a unique unsubscribe function is generated, the user directly calls this function, we delete the current subscription callback function.
// For each subscription event, a unique unsubscribe function is generated
const unSubscribe = () = > {
// Clear the subscriber callback function
delete this.eventObject[eventName][id];
};
Copy the code
Secondly, the subscribed callback function list should be replaced with object structure storage, and a unique ID should be set for each callback function. When canceling the callback function, the deletion efficiency can be improved. If the array is still used, split deletion should be used, which is less efficient than the object delete.
code
class EventBus {
constructor() {
// Initializes the event list
this.eventObject = {};
// The id of the callback function list
this.callbackId = 0;
}
// Publish events
publish(eventName, ... args) {
// Retrieve all callback functions for the current event
const callbackObject = this.eventObject[eventName];
if(! callbackObject)return console.warn(eventName + " not found!");
// Execute each callback function
for (let id in callbackObject) {
// Pass in the arguments when executing
callbackObject[id](...args);
}
}
// Subscribe to events
subscribe(eventName, callback) {
// Initialize the event
if (!this.eventObject[eventName]) {
// With object storage, cancel the callback function to improve the efficiency of deletion
this.eventObject[eventName] = {};
}
const id = this.callbackId++;
// Store the subscriber callback function
// The callbackId needs to be incremented for the next callback function
this.eventObject[eventName][id] = callback;
// For each subscription event, a unique unsubscribe function is generated
const unSubscribe = () = > {
// Clear the subscriber callback function
delete this.eventObject[eventName][id];
// If the event has no subscribers, clear the entire event object
if (Object.keys(this.eventObject[eventName]).length === 0) {
delete this.eventObject[eventName]; }};return{ unSubscribe }; }}/ / test
const eventBus = new EventBus();
// Subscribe to eventX
eventBus.subscribe("eventX".(obj, num) = > {
console.log(Module "A", obj, num);
});
eventBus.subscribe("eventX".(obj, num) = > {
console.log("Module B", obj, num);
});
const subscriberC = eventBus.subscribe("eventX".(obj, num) = > {
console.log(Module "C", obj, num);
});
// Publish event eventX
eventBus.publish("eventX", { msg: "EventX published!" }, 1);
// module C unsubscribe
subscriberC.unSubscribe();
// Publish eventX again, module C will no longer receive messages
eventBus.publish("eventX", { msg: "EventX published again!" }, 2);
/ / output> module A {msg: 'EventX published! '} 1> Module B {msg: 'EventX published! '} 1> module C {msg: 'EventX published! '} 1> module A {msg: 'EventX published again! '} 2> Module B {msg: 'EventX published again! '} 2
Copy the code
3. How to subscribe only once
If an event happens only once, it usually only needs to be subscribed once, and once the message is received, it does not need to be received again.
First of all, we provide an interface for subscribeOnce. The internal implementation is almost the same as subscribe, except that a character D is added before the callbackId to indicate that this is a subscription that needs to be deleted.
// marked as a callback that subscribes only once
const id = "d" + this.callbackId++;
Copy the code
Then, after executing the callback function, determine if the id of the current callback function is marked and if we need to remove the callback function.
// Subscribe once callback needs to be removed
if (id[0= = ="d") {
delete callbackObject[id];
}
Copy the code
code
class EventBus {
constructor() {
// Initializes the event list
this.eventObject = {};
// The id of the callback function list
this.callbackId = 0;
}
// Publish events
publish(eventName, ... args) {
// Retrieve all callback functions for the current event
const callbackObject = this.eventObject[eventName];
if(! callbackObject)return console.warn(eventName + " not found!");
// Execute each callback function
for (let id in callbackObject) {
// Pass in the arguments when executingcallbackObject[id](... args);// Subscribe once callback needs to be removed
if (id[0= = ="d") {
deletecallbackObject[id]; }}}// Subscribe to events
subscribe(eventName, callback) {
// Initialize the event
if (!this.eventObject[eventName]) {
// With object storage, cancel the callback function to improve the efficiency of deletion
this.eventObject[eventName] = {};
}
const id = this.callbackId++;
// Store the subscriber callback function
// The callbackId needs to be incremented for the next callback function
this.eventObject[eventName][id] = callback;
// For each subscription event, a unique unsubscribe function is generated
const unSubscribe = () = > {
// Clear the subscriber callback function
delete this.eventObject[eventName][id];
// If the event has no subscribers, clear the entire event object
if (Object.keys(this.eventObject[eventName]).length === 0) {
delete this.eventObject[eventName]; }};return { unSubscribe };
}
// Subscribe only once
subscribeOnce(eventName, callback) {
// Initialize the event
if (!this.eventObject[eventName]) {
// With object storage, cancel the callback function to improve the efficiency of deletion
this.eventObject[eventName] = {};
}
// marked as a callback that subscribes only once
const id = "d" + this.callbackId++;
// Store the subscriber callback function
// The callbackId needs to be incremented for the next callback function
this.eventObject[eventName][id] = callback;
// For each subscription event, a unique unsubscribe function is generated
const unSubscribe = () = > {
// Clear the subscriber callback function
delete this.eventObject[eventName][id];
// If the event has no subscribers, clear the entire event object
if (Object.keys(this.eventObject[eventName]).length === 0) {
delete this.eventObject[eventName]; }};return{ unSubscribe }; }}/ / test
const eventBus = new EventBus();
// Subscribe to eventX
eventBus.subscribe("eventX".(obj, num) = > {
console.log(Module "A", obj, num);
});
eventBus.subscribeOnce("eventX".(obj, num) = > {
console.log("Module B", obj, num);
});
eventBus.subscribe("eventX".(obj, num) = > {
console.log(Module "C", obj, num);
});
// Publish event eventX
eventBus.publish("eventX", { msg: "EventX published!" }, 1);
// Publish eventX again. Module B subscribed once and no longer receives messages
eventBus.publish("eventX", { msg: "EventX published again!" }, 2);
/ / output> module A {msg: 'EventX published! '} 1> module C {msg: 'EventX published! '} 1> Module B {msg: 'EventX published! '} 1> module A {msg: 'EventX published again! '} 2> module C {msg: 'EventX published again! '} 2
Copy the code
4. How do I clear an event or all events
We also want to clear all subscriptions to a given event with a clear operation, which is usually used when some component or module is uninstalled.
// Clear the event
clear(eventName) {
// No event name is provided. All events are cleared by default
if(! eventName) {this.eventObject = {};
return;
}
// Clear the specified event
delete this.eventObject[eventName];
}
Copy the code
The logic is similar to unsubscribe, except that it is handled uniformly.
code
class EventBus {
constructor() {
// Initializes the event list
this.eventObject = {};
// The id of the callback function list
this.callbackId = 0;
}
// Publish events
publish(eventName, ... args) {
// Retrieve all callback functions for the current event
const callbackObject = this.eventObject[eventName];
if(! callbackObject)return console.warn(eventName + " not found!");
// Execute each callback function
for (let id in callbackObject) {
// Pass in the arguments when executingcallbackObject[id](... args);// Subscribe once callback needs to be removed
if (id[0= = ="d") {
deletecallbackObject[id]; }}}// Subscribe to events
subscribe(eventName, callback) {
// Initialize the event
if (!this.eventObject[eventName]) {
// With object storage, cancel the callback function to improve the efficiency of deletion
this.eventObject[eventName] = {};
}
const id = this.callbackId++;
// Store the subscriber callback function
// The callbackId needs to be incremented for the next callback function
this.eventObject[eventName][id] = callback;
// For each subscription event, a unique unsubscribe function is generated
const unSubscribe = () = > {
// Clear the subscriber callback function
delete this.eventObject[eventName][id];
// If the event has no subscribers, clear the entire event object
if (Object.keys(this.eventObject[eventName]).length === 0) {
delete this.eventObject[eventName]; }};return { unSubscribe };
}
// Subscribe only once
subscribeOnce(eventName, callback) {
// Initialize the event
if (!this.eventObject[eventName]) {
// With object storage, cancel the callback function to improve the efficiency of deletion
this.eventObject[eventName] = {};
}
// marked as a callback that subscribes only once
const id = "d" + this.callbackId++;
// Store the subscriber callback function
// The callbackId needs to be incremented for the next callback function
this.eventObject[eventName][id] = callback;
// For each subscription event, a unique unsubscribe function is generated
const unSubscribe = () = > {
// Clear the subscriber callback function
delete this.eventObject[eventName][id];
// If the event has no subscribers, clear the entire event object
if (Object.keys(this.eventObject[eventName]).length === 0) {
delete this.eventObject[eventName]; }};return { unSubscribe };
}
// Clear the event
clear(eventName) {
// No event name is provided. All events are cleared by default
if(! eventName) {this.eventObject = {};
return;
}
// Clear the specified event
delete this.eventObject[eventName]; }}/ / test
const eventBus = new EventBus();
// Subscribe to eventX
eventBus.subscribe("eventX".(obj, num) = > {
console.log(Module "A", obj, num);
});
eventBus.subscribe("eventX".(obj, num) = > {
console.log("Module B", obj, num);
});
eventBus.subscribe("eventX".(obj, num) = > {
console.log(Module "C", obj, num);
});
// Publish event eventX
eventBus.publish("eventX", { msg: "EventX published!" }, 1);
/ / remove
eventBus.clear("eventX");
// Publish eventX again, since it has been cleared, all modules will no longer receive messages
eventBus.publish("eventX", { msg: "EventX published again!" }, 2);
/ / output> module A {msg: 'EventX published! '} 1> Module B {msg: 'EventX published! '} 1> module C {msg: 'EventX published! '} 1
> eventX not found!
Copy the code
5. The TypeScript version
Given the widespread adoption of TypeScript, especially on large front-end projects, we’ll briefly change to a version of TypeScript
You can copy the following code to the TypeScript Playground to experience how it works
code
interface ICallbackList {
[id: string] :Function;
}
interface IEventObject {
[eventName: string]: ICallbackList;
}
interface ISubscribe {
unSubscribe: () = > void;
}
interface IEventBus {
publish<T extends any[]>(eventName: string. args: T):void;
subscribe(eventName: string.callback: Function): ISubscribe;
subscribeOnce(eventName: string.callback: Function): ISubscribe;
clear(eventName: string) :void;
}
class EventBus implements IEventBus {
private _eventObject: IEventObject;
private _callbackId: number;
constructor() {
// Initializes the event list
this._eventObject = {};
// The id of the callback function list
this._callbackId = 0;
}
// Publish events
publish<T extends any[]>(eventName: string. args: T):void {
// Retrieve all callback functions for the current event
const callbackObject = this._eventObject[eventName];
if(! callbackObject)return console.warn(eventName + " not found!");
// Execute each callback function
for (let id in callbackObject) {
// Pass in the arguments when executingcallbackObject[id](... args);// Subscribe once callback needs to be removed
if (id[0= = ="d") {
deletecallbackObject[id]; }}}// Subscribe to events
subscribe(eventName: string.callback: Function): ISubscribe {
// Initialize the event
if (!this._eventObject[eventName]) {
// With object storage, cancel the callback function to improve the efficiency of deletion
this._eventObject[eventName] = {};
}
const id = this._callbackId++;
// Store the subscriber callback function
// The callbackId needs to be incremented for the next callback function
this._eventObject[eventName][id] = callback;
// For each subscription event, a unique unsubscribe function is generated
const unSubscribe = () = > {
// Clear the subscriber callback function
delete this._eventObject[eventName][id];
// If the event has no subscribers, clear the entire event object
if (Object.keys(this._eventObject[eventName]).length === 0) {
delete this._eventObject[eventName]; }};return { unSubscribe };
}
// Subscribe only once
subscribeOnce(eventName: string.callback: Function): ISubscribe {
// Initialize the event
if (!this._eventObject[eventName]) {
// With object storage, cancel the callback function to improve the efficiency of deletion
this._eventObject[eventName] = {};
}
// marked as a callback that subscribes only once
const id = "d" + this._callbackId++;
// Store the subscriber callback function
// The callbackId needs to be incremented for the next callback function
this._eventObject[eventName][id] = callback;
// For each subscription event, a unique unsubscribe function is generated
const unSubscribe = () = > {
// Clear the subscriber callback function
delete this._eventObject[eventName][id];
// If the event has no subscribers, clear the entire event object
if (Object.keys(this._eventObject[eventName]).length === 0) {
delete this._eventObject[eventName]; }};return { unSubscribe };
}
// Clear the event
clear(eventName: string) :void {
// No event name is provided. All events are cleared by default
if(! eventName) {this._eventObject = {};
return;
}
// Clear the specified event
delete this._eventObject[eventName]; }}/ / test
interface IObj {
msg: string;
}
type PublishType = [IObj, number];
const eventBus = new EventBus();
// Subscribe to eventX
eventBus.subscribe("eventX".(obj: IObj, num: number, s: string) = > {
console.log(Module "A", obj, num);
});
eventBus.subscribe("eventX".(obj: IObj, num: number) = > {
console.log("Module B", obj, num);
});
eventBus.subscribe("eventX".(obj: IObj, num: number) = > {
console.log(Module "C", obj, num);
});
// Publish event eventX
eventBus.publish("eventX", { msg: "EventX published!" }, 1);
/ / remove
eventBus.clear("eventX");
// Publish eventX again, since it has been cleared, all modules will no longer receive messages
eventBus.publish<PublishType>("eventX", { msg: "EventX published again!" }, 2);
/ / output
[LOG]: Module "A", {
"msg": "EventX published!"
}, 1
[LOG]: "Module B", {
"msg": "EventX published!"
}, 1
[LOG]: Module "C", {
"msg": "EventX published!"
}, 1
[WRN]: "eventX not found!"
Copy the code
6. Singleton mode
In practice, only one event bus is often required to meet the requirements, and there are two cases, singletons and global singletons that remain in the upper instance.
- Keep singletons in upper-level instances
To introduce the EventBus to the upper-layer instance, you only need to ensure that there is only one EventBus in one upper-layer instance. If there are multiple upper-layer instances, it means that there are multiple event buses, but each upper-layer instance controls its own EventBus. A variable is created in the upper instance to store the event bus, which is initialized only when it is used for the first time. The event bus instance is acquired directly when other modules use the event bus.
code
// Upper-level instance
class LWebApp {
private_eventBus? : EventBus;constructor() {}
public getEventBus() {
// Initialization for the first time
if (this._eventBus == undefined) {
this._eventBus = new EventBus();
}
// Take a single instance each time and keep it in the LWebApp instance
return this._eventBus; }}/ / use
const eventBus = new LWebApp().getEventBus();
Copy the code
- Global singleton
Sometimes we want no matter which module wants to use our event bus, we want those modules to use the same instance. This is a global singleton. This design makes it easier to manage events uniformly.
Write the same as above, except that _eventBus and getEventBus are static. Use static methods instead of instantiating the EventBusTool tool class.
code
// Upper-level instance
class EventBusTool {
private static_eventBus? : EventBus;constructor() {}
public static getEventBus(): EventBus {
// Initialization for the first time
if (this._eventBus == undefined) {
this._eventBus = new EventBus();
}
// Take a unique instance each time to keep the global singleton
return this._eventBus; }}/ / use
const eventBus = EventBusTool.getEventBus();
Copy the code
Original text: dushusir.com/js-event-bu…
conclusion
The above is the small editor’s understanding of the Event Bus, which basically achieves the desired effect. By implementing the publish/subscribe pattern yourself, you also deepen your understanding of classic design patterns. There are still many deficiencies and need to be optimized, welcome to share their experience.
reference
- How do I implement Event Bus in JavaScript
- How to Implement an Event Bus in TypeScript
- Implement EventBus with JS
- Vue EventBus (Vue EventBus