Implement an EventEmitter class — the small pub/sub primitive that backs Node's events module, the DOM's event targets, and most front-end state buses. Other code registers listeners by name with on, removes them with off, and triggers every listener for a name with emit.
class EventEmitter {
on(event, listener) // register listener under `event`
off(event, listener) // unregister this exact listener under `event`
emit(event, ...args) // call every listener registered for `event`, forwarding args
}
event is a string. listener is a function. Methods don't need to return anything.
Register and fire:
const bus = new EventEmitter();
bus.on('login', (user) => console.log('hi', user));
bus.emit('login', 'ada'); // logs: hi ada
bus.emit('logout'); // no listeners — silent no-op
Multiple listeners + multiple args + off:
const bus = new EventEmitter();
const a = (x, y) => console.log('a', x + y);
const b = (x, y) => console.log('b', x * y);
bus.on('math', a);
bus.on('math', b);
bus.emit('math', 2, 3); // logs: a 5 then b 6
bus.off('math', a);
bus.emit('math', 2, 3); // logs: b 6
emit on an unknown event is a no-op, not an error.off is forgiving — unregistering a listener that was never registered (or a whole event that doesn't exist) is a no-op, not an error.emit after the event name reaches each listener as positional arguments.once, wildcards, or async listeners — those belong to the richer variant. Synchronous, one event per call is enough here.