Implement Event Emitter
Interview Question: "Implement an EventEmitter class with on, off, emit, and once methods."
The Implementation
class EventEmitter {
#listeners = new Map();
on(event, callback) {
if (!this.#listeners.has(event)) {
this.#listeners.set(event, []);
}
this.#listeners.get(event).push(callback);
return this;
}
off(event, callback) {
const callbacks = this.#listeners.get(event);
if (!callbacks) return this;
this.#listeners.set(
event,
callbacks.filter(cb => cb !== callback && cb._original !== callback)
);
return this;
}
emit(event, ...args) {
const callbacks = this.#listeners.get(event);
if (!callbacks) return false;
callbacks.forEach(cb => cb.apply(this, args));
return true;
}
once(event, callback) {
const wrapper = (...args) => {
callback.apply(this, args);
this.off(event, wrapper);
};
wrapper._original = callback;
this.on(event, wrapper);
return this;
}
removeAllListeners(event) {
if (event) {
this.#listeners.delete(event);
} else {
this.#listeners.clear();
}
return this;
}
listenerCount(event) {
return this.#listeners.get(event)?.length ?? 0;
}
}Key points:
oncewrapper â_originalreference allowsoffto find and removeoncelisteners- Method chaining â
return thisenablesemitter.on('a', fn1).on('b', fn2) emitreturns boolean â Indicates whether any listeners were called- Private
#listenersâ Encapsulation via private class field
Usage
const emitter = new EventEmitter();
function onData(data) { console.log('Received:', data); }
emitter.on('data', onData);
emitter.once('connect', () => console.log('Connected!'));
emitter.emit('connect'); // "Connected!"
emitter.emit('connect'); // (nothing â once handler removed)
emitter.emit('data', { id: 1 }); // "Received: { id: 1 }"
emitter.off('data', onData);
emitter.emit('data', { id: 2 }); // (nothing)Follow-Up: "How would you add a max listeners warning?"
constructor(maxListeners = 10) {
this.#maxListeners = maxListeners;
}
on(event, callback) {
// ... existing code ...
if (this.#listeners.get(event).length > this.#maxListeners) {
console.warn(`Possible memory leak: ${event} has ${this.listenerCount(event)} listeners`);
}
return this;
}This mirrors Node.js's EventEmitter behavior â warning about potential memory leaks.