Ein Event-Bus ist ein Entwurfsmuster (und obwohl wir hier über JavaScript sprechen, ist es ein Entwurfsmuster in jeder Sprache), das zur Vereinfachung der Kommunikation zwischen verschiedenen Komponenten verwendet werden kann. Es kann auch als Publish/Subscribe oder Pubsub betrachtet werden.
Die Idee ist, dass Komponenten auf den Event-Bus lauschen können, um zu wissen, wann sie ihre Aufgaben erledigen sollen. Zum Beispiel könnte eine "Tabellen"-Komponente auf Ereignisse lauschen, die ihr sagen, dass sie den aktiven Tab wechseln soll. Sicher, das könnte durch einen Klick auf einen der Tabs geschehen und somit vollständig innerhalb dieser Komponente behandelt werden. Aber mit einem Event-Bus könnten einige *andere* Elemente dem Tab mitteilen, zu wechseln. Stellen Sie sich die Übermittlung eines Formulars vor, das einen Fehler verursacht, auf den der Benutzer in einem bestimmten Tab aufmerksam gemacht werden muss, sodass das Formular eine Nachricht an den Event-Bus sendet, die der Tabs-Komponente mitteilt, den aktiven Tab zu dem mit dem Fehler zu wechseln. Das ist, wie es an Bord eines Event-Busses aussieht.
Pseudo-Code für diese Situation wäre etwa so…
// Tab Component
Tabs.changeTab = id => {
// DOM work to change the active tab.
}
MyEventBus.subscribe("change-tab", Tabs.changeTab(id));
// Some other component...
// something happens, then:
MyEventBus.publish("change-tab", 2);
Brauchen Sie eine JavaScript-Bibliothek dafür? (Fangfrage: Sie *brauchen nie* eine JavaScript-Bibliothek). Nun, es gibt viele Optionen da draußen
- PubSubJS
- EventEmitter3
- Postal.js
- jQuery hat sogar benutzerdefinierte Ereignisse unterstützt, was eng mit diesem Muster verwandt ist.
Schauen Sie sich auch Mitt an, eine Bibliothek, die nur 200 Bytes komprimiert ist. Es hat etwas an diesem einfachen Muster, das Menschen dazu inspiriert, es selbst auf die prägnanteste Weise anzugehen.
Lassen Sie uns das selbst tun! Wir werden überhaupt keine Drittanbieterbibliothek verwenden und ein ereignislauschendes System nutzen, das bereits in JavaScript integriert ist, mit dem addEventListener, das wir alle kennen und lieben.
Zuerst ein wenig Kontext
Die addEventListener API in JavaScript ist eine Member-Funktion der EventTarget Klasse. Der Grund, warum wir ein click-Ereignis an einen Button binden können, ist, dass die Prototyp-Schnittstelle von <button> (HTMLButtonElement) indirekt von EventTarget erbt.

Im Gegensatz zu den meisten anderen DOM-Schnittstellen kann EventTarget *direkt* mit dem Schlüsselwort new erstellt werden. Es wird in allen modernen Browsern unterstützt, aber erst ziemlich kürzlich. Wie wir im obigen Screenshot sehen können, erbt Node von EventTarget, somit haben alle DOM-Knoten die Methode addEventListener.
Hier ist der Trick
Ich schlage einen extrem leichten Node-Typ vor, der als unser ereignislauschender Bus dient: ein HTML-Kommentar (<!-- Kommentar -->.
Für eine Browser-Rendering-Engine sind HTML-Kommentare nur Notizen im Code, die keine Funktionalität haben, abgesehen von beschreibendem Text für Entwickler. Aber da Kommentare immer noch in HTML geschrieben sind, landen sie im DOM als echte Knoten und haben ihre eigene Prototyp-Schnittstelle – Comment – die von Node erbt.
Die Comment-Klasse kann mit new direkt erstellt werden, so wie EventTarget auch
const myEventBus = new Comment('my-event-bus');
Wir könnten auch die alte, aber weit verbreitete API document.createComment verwenden. Sie erfordert einen data-Parameter, der den Inhalt des Kommentars darstellt. Er kann sogar ein leerer String sein.
const myEventBus = document.createComment('my-event-bus');
Jetzt können wir Ereignisse mit dispatchEvent auslösen, was ein Event-Objekt akzeptiert. Um benutzerdefinierte Ereignisdaten zu übergeben, verwenden Sie CustomEvent, wobei das detail-Feld verwendet werden kann, um beliebige Daten zu enthalten.
myEventBus.dispatchEvent(
new CustomEvent('event-name', {
detail: 'event-data'
})
);
Internet Explorer 9-11 unterstützt CustomEvent, aber keine der Versionen unterstützt new CustomEvent. Es ist komplex, es mit document.createEvent zu simulieren. Wenn IE-Unterstützung für Sie wichtig ist, gibt es eine Möglichkeit, ein Polyfill dafür zu erstellen.
Jetzt können wir Ereignis-Listener binden
myEventBus.addEventListener('event-name', ({ detail }) => {
console.log(detail); // => event-data
});
Wenn ein Ereignis nur einmal ausgelöst werden soll, können wir { once: true } für die einmalige Bindung verwenden. Andere Optionen passen hier nicht. Um Ereignis-Listener zu entfernen, können wir das native removeEventListener verwenden.
Debugging
Die Anzahl der an einen einzigen Event-Bus gebundenen Ereignisse kann riesig sein. Es kann auch zu Speicherlecks kommen, wenn Sie vergessen, sie zu entfernen. Was ist, wenn wir wissen wollen, *wie viele* Ereignisse an myEventBus gebunden sind?
myEventBus ist ein DOM-Knoten, sodass er von den DevTools im Browser inspiziert werden kann. Von dort aus können wir die Ereignisse im Reiter Elemente → Ereignis-Listener finden. Stellen Sie sicher, dass Sie "Vorfahren" abwählen, um Ereignisse auszublenden, die an document und window gebunden sind.

Ein Beispiel
Ein Nachteil ist, dass die Syntax von EventTarget etwas umständlich ist. Wir können einen einfachen Wrapper dafür schreiben. Hier ist ein Demo in TypeScript unten
class EventBus<DetailType = any> {
private eventTarget: EventTarget;
constructor(description = '') { this.eventTarget = document.appendChild(document.createComment(description)); }
on(type: string, listener: (event: CustomEvent<DetailType>) => void) { this.eventTarget.addEventListener(type, listener); }
once(type: string, listener: (event: CustomEvent<DetailType>) => void) { this.eventTarget.addEventListener(type, listener, { once: true }); }
off(type: string, listener: (event: CustomEvent<DetailType>) => void) { this.eventTarget.removeEventListener(type, listener); }
emit(type: string, detail?: DetailType) { return this.eventTarget.dispatchEvent(new CustomEvent(type, { detail })); }
}
// Usage
const myEventBus = new EventBus<string>('my-event-bus');
myEventBus.on('event-name', ({ detail }) => {
console.log(detail);
});
myEventBus.once('event-name', ({ detail }) => {
console.log(detail);
});
myEventBus.emit('event-name', 'Hello'); // => Hello Hello
myEventBus.emit('event-name', 'World'); // => World
Die folgende Demo liefert das kompilierte JavaScript.
Und da haben wir es! Wir haben gerade einen abhängigkeitsfreien ereignislauschenden Bus erstellt, bei dem eine Komponente eine andere Komponente über Änderungen informieren kann, um eine Aktion auszulösen. Es braucht keine vollständige Bibliothek für solche Dinge, und die Möglichkeiten, die sich dadurch eröffnen, sind schier endlos.
Cool :)
Bitte erwägen Sie zwei weitere Optionen, bevor Sie diese Art von Ansatz wählen
Verwenden Sie einfach das Fensterobjekt, das bereits vorhanden ist. Ihr Wrapper fügt keine neue Funktionalität hinzu, und wenn Sie mit der Standard-Browser-API noch nicht vertraut sind, ist jetzt ein guter Zeitpunkt, um sie zu lernen. Die Verwendung des Fensterobjekts ermöglicht es Listenern, sich zu registrieren, bevor das Skript, das Ereignisse auslöst, überhaupt geladen wurde – während Ihr Wrapper zu einer neuen Abhängigkeit wird, die Abonnenten laden oder irgendwie lokalisieren müssen. Verwenden Sie Ihren Herstellernamen als Präfix für den Ereignisnamen, um sicherzustellen, dass Sie nicht mit Skripten von Drittanbietern kollidieren, die möglicherweise denselben Bus verwenden.
Wenn Sie für Node entwickeln, wo beliebte Pakete dafür bereits verfügbar sind, und Sie immer noch darauf bestehen, es selbst zu machen, erfordert die Verwendung der Standard-Map- und Set-Klassen zur Speicherung von Listenern nur wenige zusätzliche Codezeilen. Dies funktioniert mit allem, was JavaScript außerhalb des Browsers ausführt.
Wenn Sie dies für "kürzere Methodennamen" tun, tun Sie es aus den falschen Gründen – wenn Sie für den Browser entwickeln, werden Sie fast sicher die nativen Ereignis-Features verwenden müssen, und es bringt nichts, ein identisches Feature mit inkonsistenten Namen zu erstellen.
Wenn Sie sich nur an den Browser richten, ist das gesuchte Feature bereits vorhanden und vollständig standardisiert. Wenn wir uns bei diesen Dingen einig werden, schaffen wir nur unnötige Komplexität und eine zusätzliche Lernkurve für die nächste Person, die das Produkt warten muss.
Wählen Sie immer die Einfachheit, wenn Sie die Wahl haben.
Der DOM, den Sie als Event-Bus verwenden, sollte sauber sein. Ich glaube nicht, dass die Verwendung von Vendor-Präfixen eine gute Idee ist, da sie umständlich ist und immer noch Möglichkeiten für Konflikte mit anderen Ereignissen bietet.
Meiner Meinung nach sollte der Event-Bus-DOM nicht selbst native Ereignisse auslösen, keine Auswirkungen auf den DOM-Baum haben und nicht von anderen 3rd-Party-Bibliotheken wiederverwendet werden.
Die Verwendung von window ist meiner Meinung nach die schlechteste Wahl, da jedes auf der Seite ausgelöste Ereignis an window weitergeleitet wird und es eine riesige Menge von 3rd-Party-Bibliotheken gibt, die Ereignisse an window binden, was zu Problemen führen kann.
Sie können sich einen Event-Bus wie Hooks in WordPress vorstellen. Dieser verwendet natives JavaScript, aber nicht die Event-API, sodass er mehr Browser unterstützt. Geeignet zur Erweiterung in einer kleinen Anwendung
https://github.com/taufik-nurrohman/hook
Ich habe nicht verstanden, warum ein Kommentar-Knoten erstellt werden soll, anstatt den "Container" der Komponente zu verwenden, da er bereits existiert?
Da sie native Ereignisse auslösen können, die mit anderen Abonnenten interferieren können
Was ist der Unterschied zwischen dem Erstellen von "new Comment" (oder einem anderen DOM-Element) und dem direkten Erstellen einer neuen EventTarget-Instanz über
new EventTarget(), abgesehen von der Abwärtskompatibilität mit IE?Ich habe es noch nicht ausprobiert, aber das werde ich bald tun.
Ich finde die Idee, einen Kommentar für einen Event-Bus zu verwenden, verrückt und spaßig. Ich wäre nicht darauf gekommen, also danke dafür. Ich habe heute etwas Neues gelernt.
Da ein Kommentar von Browser-DevTools inspiziert werden kann, was für das Debugging nützlich ist.
Siehe Debugging-Abschnitt
Ich verwende die gleiche Technik mit einem leeren
div. Danke für die detaillierte Erklärung. Jetzt weiß ich, dass ich mit einer reinen EventTarget-Instanz noch leichter sein kann. (Ich muss IE11 nicht unterstützen.)Ich mag deine Serie.
Aber warum ist der Titel "...Native Event Bus in JavaScript", aber du endest mit Typescript?
Was ist das JavaScript-Klassenbeispiel?
Nun, TypeScript ist typischerweise JavaScript mit Typen. Entfernen Sie einfach diese Typ-Assertions, und Sie erhalten JavaScript.
Es ist nicht notwendig, einen DOM-Kommentar zu verwenden, um von EventTarget zu profitieren. Hier ist mein Vorschlag für den EventBus
Vielen Dank für das Teilen des Codes zur Erweiterung von EventTarget. Ich glaube, der Autor hat die Verwendung eines DOM-Kommentars wegen mangelnder Browserunterstützung für EventTarget vorgeschlagen.
Das ist GENAU das, wonach ich gesucht habe. Ich bin Softwareentwickler mit Schwerpunkt Daten und absoluter Neuling in der TypeScript-Entwicklung. Ich habe eine Frage. Weiß jemand, ob es einen Grund gibt, warum dies NICHT funktionieren würde, um die Kommunikation zwischen 3rd-Party-Bibliotheken wie Synchfusion oder Telerik herzustellen? Ich habe große Schwierigkeiten, die plattformübergreifende Methodenaufrufung zu ermöglichen.
Die Methode `off` scheint nicht zu funktionieren
Ich mag die Idee, einen Kommentar als Event-Bus zu verwenden, sehr, weil man sicher sein kann, dass er nicht mit dem Layout kollidiert. Aber wie wähle ich diesen Kommentar per JavaScript aus?