🧠 Concepts and Explanations
- There is no
events
object in JavaScript, but we fake it in Node.js.
- An event emitter helps avoid writing a lot of
if-else
statements. Instead, you just maintain an array for each event.
🛠 Manual Event Emitter Implementation
// Create a custom emitter
export function emitter() {
this.events = {
prop: []
};
}
emitter.prototype.on = function(event, listener) {
this.events[event] = this.events[event] || [];
this.events[event].push(listener);
}
emitter.prototype.emit = function(event) {
if (this.events[event]) {
this.events[event].forEach(function(listener) {
listener();
});
}
}
// Example usage:
const eventEmitter = new emitter();
eventEmitter.on("greet", function() {
console.log("Hello, world!");
});
eventEmitter.on("greet", function() {
console.log("A greeting event occurred!");
});
eventEmitter.emit("greet");
💡 Notes
- We implemented it manually, but Node.js has its own
EventEmitter
that has the same functionality, but is more efficient and powerful.
⚠️ Magic Strings
- A magic string is a string that has special meaning in our code. This can sometimes lead to bugs if mistyped.
- A common pattern is to store these strings in an object and refer to them through constants to change it in one place instead of multiple places.
// Example of using magic strings via config object
module.exports = {
events: {
GREET: 'greet'
}
}
// Usage
eventEmitter.on(eventConfig.events.GREET, function() {
console.log("Hello, world!");
});
🔗 Prototype Chaining
- With prototype chaining, you can inherit data and methods from another module.
const util = require('util');
function GREET() {
this.greeting = "Hello World";
}
// Inherit from emitter
util.inherits(GREET, emitter);
// Add custom method
GREET.prototype.greet = function(data) {
console.log(this.greeting + " " + data);
this.emit("greet", data);
}
// Usage
const greet1 = new GREET();
greet1.on("greet", function(data) {
console.log("Someone greeted: " + data);
});
greet1.greet("John Doe");
📞 Call vs Apply vs Invoke
- The difference between a
.call()
and a normal function invocation is that you can pass parameters directly using .call(args)
.
- The difference between
.call()
and .apply()
is:
.call(arg1, arg2, ...)
uses comma-separated arguments.
.apply([arg1, arg2, ...])
uses an array of arguments.
📦 Inheriting Constructor Behavior
- When using
inherits()
, it doesn't copy the parent constructor, so you have to manually call it inside the child constructor:
emitter.call(this); // Run the parent constructor