-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathEventEmitter.js
More file actions
123 lines (106 loc) · 3.15 KB
/
Copy pathEventEmitter.js
File metadata and controls
123 lines (106 loc) · 3.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/**
* @fileoverview
* Custom Event Emitter (Pub/Sub Pattern)
* A classic senior JavaScript interview question.
*
* Target: Implement a class that allows subscribing to events,
* emitting events, and unsubscribing.
*/
class EventEmitter {
constructor() {
// Stores event names as keys and arrays of callbacks as values
this.events = {};
}
/**
* Subscribe to an event
* @param {string} eventName
* @param {Function} callback
* @returns {Function} Unsubscribe function (convenience)
*/
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callback);
// Return an unsubscribe function for easier cleanup
return () => this.off(eventName, callback);
}
/**
* Emit an event (trigger all callbacks)
* @param {string} eventName
* @param {...any} args
*/
emit(eventName, ...args) {
if (!this.events[eventName]) return;
this.events[eventName].forEach(callback => {
callback.apply(this, args);
});
}
/**
* Unsubscribe from an event
* @param {string} eventName
* @param {Function} callback
*/
off(eventName, callback) {
if (!this.events[eventName]) return;
this.events[eventName] = this.events[eventName].filter(
cb => cb !== callback
);
}
/**
* Subscribe to an event once (removes itself after first trigger)
* @param {string} eventName
* @param {Function} callback
*/
once(eventName, callback) {
const wrapper = (...args) => {
callback(...args);
this.off(eventName, wrapper);
};
this.on(eventName, wrapper);
}
/**
* Remove all listeners for an event or all events
* @param {string} [eventName]
*/
removeAll(eventName) {
if (eventName) {
delete this.events[eventName];
} else {
this.events = {};
}
}
}
/**
* 📈 Interview Insights:
* -----------------------
* 1. Why use apply/spread? To pass variable arguments to the listener.
* 2. Why return unsubscribe? In frameworks like React, this makes
* useEffect cleanup trivial.
* 3. Memory Leaks: Explain that failing to 'off' listeners when a component
* unmounts causes memory leaks (the emitter still holds a reference to the closure).
* 4. Complexity:
* - on/off: O(1) or O(L) where L is listeners count.
* - emit: O(L).
*/
// ------------------------------------
// 🧪 Test Cases
// ------------------------------------
const emitter = new EventEmitter();
const greet = (name) => console.log(`Hello, ${name}!`);
const farewell = () => console.log("Goodbye!");
console.log("--- Standard on/emit ---");
emitter.on('greet', greet);
emitter.emit('greet', 'Alice'); // "Hello, Alice!"
console.log("\n--- Once ---");
emitter.once('farewell', farewell);
emitter.emit('farewell'); // "Goodbye!"
emitter.emit('farewell'); // (Nothing happens)
console.log("\n--- Off/Unsubscribe ---");
const unsub = emitter.on('greet', (name) => console.log(`Yo, ${name}`));
emitter.emit('greet', 'Bob');
// "Hello, Bob!"
// "Yo, Bob"
unsub(); // Using returned function
emitter.off('greet', greet); // Using explicit off
emitter.emit('greet', 'Charlie'); // (Nothing happens)