src/EventEmitter.js
class EventEmitter {
constructor() {
// This is an Object containing Maps:
//
// { [event: string]: Map<listener: function, numTimesAdded: number> }
//
// We use a Map for O(1) insertion/deletion and because it can have functions as keys.
//
// We keep track of numTimesAdded (the number of times it was added) because if you attach the same listener twice,
// we should actually call it twice for each emitted event.
this.observers = {};
}
on(events, listener) {
events.split(' ').forEach((event) => {
if (!this.observers[event]) this.observers[event] = new Map();
const numListeners = this.observers[event].get(listener) || 0;
this.observers[event].set(listener, numListeners + 1);
});
return this;
}
off(event, listener) {
if (!this.observers[event]) return;
if (!listener) {
delete this.observers[event];
return;
}
this.observers[event].delete(listener);
}
emit(event, ...args) {
if (this.observers[event]) {
const cloned = Array.from(this.observers[event].entries());
cloned.forEach(([observer, numTimesAdded]) => {
for (let i = 0; i < numTimesAdded; i++) {
observer(...args);
}
});
}
if (this.observers['*']) {
const cloned = Array.from(this.observers['*'].entries());
cloned.forEach(([observer, numTimesAdded]) => {
for (let i = 0; i < numTimesAdded; i++) {
observer.apply(observer, [event, ...args]);
}
});
}
}
}
export default EventEmitter;