I have a child class which extends parent class. I also want child class to be able to emit events. Examples I found were extending EventEmitter. But JS does not allow multiple inheritance. I tried to implement it via mixin but not sure how to do without converting my parent class to a mixin.
Example
Say there is a base class for vehicles all vehicles move and some can emit events when they move
class Vehicle {
constructor(name) { this.name = name }
move() { console.log(this.name, 'Moved'); }
}
class Car extends Vehicle {
constructor() { super('Ford'); }
move() {
super.move();
this.engineSound();
}
engineSound() {
console.log("Vroooom!");
}
// some other car related properties
}
class Ambulance extends Vehicle { // want to extend EventEmitter aswell
constructor() { super('Ambulance1'); }
move() {
super.move();
// emit moved event here
}
}
let normalVehicle = new Vehicle('Normal');
normalVehicle.move();
let car = new Car();
car.move();
let ambulance = new Ambulance();
// Listen for moving ambulance here
// ambulance.on('moved', () => { console.log('Ambulance coming') });
ambulance.move();
But to emit events docs suggest extending EventEmitter but JS does not support extending multiple classes.
Saw few examples which were using Object.assign
but not sure how that can be applied here.
CodePudding user response:
es6 class syntax doesn't support multiple inheritance at all. You can try weird trickery if you convert all classes to es5 constructor functions and then use things like Object.assign. But the best approach would be to use composition over inheritance.
OOP is used to try and mimic real world objects, so in real world terms it also doesn't make sense for an ambulance to be itself an event emitter.
However, using a composition approach, it makes sense for an ambulance to have an event emitter, like a radio or other such transmission device:
class Ambulance extends Vehicle {
#radio = new EventEmitter();
constructor() {
super("Ambulance");
}
emit(event) { this.#radio.emit(event) }
on(event, handler) { this.#radio.on(event, handler) }
move() {
super.move();
this.emit("moved");
}
}
let ambulance = new Ambulance();
ambulance.on('moved', () => { console.log('Ambulance coming') });
ambulance.move();
Note that the radio is private (using the new JS private fields feature), and I only supply a facade for the exact functionality I want the ambulance users to have, in this case 'on' and 'emit'.
With that said...
Lets get crazy and actually answer your question! Again, this will require to use es5 constructor functions:
// Base class - no inheritance
var Vehicle = (function () {
function Vehicle(name) {
this.name = name;
}
Vehicle.prototype.move = function () {
return this.name " Moved\n";
};
return Vehicle;
})();
// Derived from Vehicle - regular inheritance from a single object
var Car = (function (_super) {
function Car() {
// Calling super() in constructor
_super.call(this, "Ford");
}
// extend Vehicle
Car.prototype = Object.create(Vehicle.prototype);
// bring back Car's constructor, because last step overrid it
Object.defineProperty(Car.prototype, "constructor", {
value: Car,
enumerable: false,
writable: true,
configurable: true
});
Car.prototype.move = function () {
return _super.prototype.move.call(this) this.engineSound();
};
Car.prototype.engineSound = function () {
return "Vroooom!\n";
};
return Car;
})(Vehicle);
// Derived from Vehicle and EventEmitter - multiple inheritance!
var Ambulance = (function (_superVehicle, _superEventEmitter) {
function Ambulance() {
_superVehicle.call(this, "Ambulance");
_superEventEmitter.call(this);
}
// Create joint prototype using Object.assign
var jointPrototype = Object.assign(
{},
_superEventEmitter.prototype,
_superVehicle.prototype
);
// Connecting Ambulance to prototype chain
Ambulance.prototype = Object.create(jointPrototype);
// restore Ambulance's constructor
Object.defineProperty(Ambulance.prototype, "constructor", {
value: Ambulance,
enumerable: false,
writable: true,
configurable: true
});
Ambulance.prototype.move = function () {
this.emit("moved");
return _superVehicle.prototype.move.call(this);
};
return Ambulance;
})(Vehicle, EventEmitter);