I have 2 base classes, say ParentClass1
and ParentClass2
. Now I want to do multiple prototypical inheritance to ChildClass
.
With a single parent class, I had my code as follows.
var ParentClass = function() {
};
ParentClass.prototype.greetUser = function(name) {
console.log('Hi. Hello,', name);
};
var ChildClass = function(name) {
this.greetUser(name);
};
ChildClass.prototype = Object.create(ParentClass.prototype);
var obj = new ChildClass('John');
// Hi. Hello, John
And now when I have to inherit from 2 parent classes, I tried the following code.
var ParentClass1 = function() {
};
ParentClass1.prototype.greetUser = function(name) {
console.log('Hi. Hello,', name);
};
var ParentClass2 = function() {
};
ParentClass2.prototype.askUser = function(name) {
console.log('Hey, how are you,', name);
};
var ChildClass = function(name) {
this.askUser(name);
this.greetUser(name);
};
ChildClass.prototype = Object.create(ParentClass1.prototype);
ChildClass.prototype = Object.create(ParentClass2.prototype);
var obj = new ChildClass('John');
// Error.
But this seems like, this will only accept the last mentioned Object.create()
.
So later, It tried switching the second Object.create()
to Object.assign()
, and then it worked fine.
ChildClass.prototype = Object.create(ParentClass1.prototype);
ChildClass.prototype = Object.assign(ChildClass.prototype, ParentClass2.prototype);
But my concern is Object.assign()
is doing a clone. So is that the right way to do it? Or are there any better alternatives?
Sorry for making this question lengthy. I really appreciate any help you can provide.
It is not very useful to do two assignments to the prototype
property, as the last one will overwrite the first. You can do like this, since Object.assign
accepts more arguments:
Object.assign(ChildClass.prototype, ParentClass1.prototype, ParentClass2.prototype);
Note that Object.assign
performs a shallow copy. That a copy has to be made is sure: you need a prototype object that is different from both other prototypes: the union of both. So inevitably you need to somehow copy the members of the parent prototypes into your target prototype object.
Object.assign
makes a shallow copySince Object.assign
performs a shallow copy, you might get into cases where you interfere with the parent prototype. This might be what you want or do not want.
Example:
var ParentClass1 = function() {
};
ParentClass1.prototype.userList = [];
ParentClass1.prototype.addUser = function(name) {
this.userList.push(name);
};
var ParentClass2 = function() {
};
ParentClass2.prototype.askUser = function(name) {
console.log('Hey, how are you,', name);
};
var ChildClass = function(name) {
this.askUser(name);
};
Object.assign(ChildClass.prototype, ParentClass1.prototype, ParentClass2.prototype);
var p = new ParentClass1('Parent');
var obj = new ChildClass('John');
obj.addUser('Tim'); // Added to child, but
console.log(p.userList); // now parent also has Tim...
Object.assign
only copies enumerable propertiesThis means that in some cases you will not get the properties you had hoped for. Say you wanted to inherit also from Array.prototype
, then you would want your child object to have a length
property, but since it is not enumerable, you will not get it with Object.assign
:
var ParentClass2 = function() {
};
ParentClass2.prototype.askUser = function(name) {
console.log('Hey, how are you,', name);
};
var ChildClass = function(name) {
this.askUser(name);
};
Object.assign(ChildClass.prototype, Array.prototype, ParentClass2.prototype);
var obj = new ChildClass('John');
console.log(obj.length); // undefined
console.log(Array.prototype.length); // 0
Object.assign
executes gettersObject.assign
cannot copy getters. Instead it executes them to retrieve the value for the copy. Executing code on the parent prototype may have effects (by design of that getter) on the state of the parent prototype. This might be undesired behaviour in the context of this copy.
Secondly, the value of the getter can be the result of some calculation and state of the object, returning different values each time it is referenced. But object.assign
will only reference it once, and then create a property that always has that single value. See the effect in this example:
var ParentClass1 = function() {
};
// Define a getter on the prototype which returns a
// random number between 0 and 999, every time it is referenced:
Object.defineProperty(ParentClass1.prototype, 'randomNumber', {
get: function() {
return Math.round(Math.random() * 1000);
},
enumerable: true
});
var ParentClass2 = function() {};
ParentClass2.prototype.askUser = function(name) {
console.log('Hey, how are you,', name);
};
var ChildClass = function(name) {
this.askUser(name);
};
Object.assign(ChildClass.prototype, ParentClass1.prototype, ParentClass2.prototype);
var p = new ParentClass1('Parent');
var obj = new ChildClass('John');
console.log('different:');
console.log(p.randomNumber);
console.log(p.randomNumber);
console.log(p.randomNumber);
console.log('always same:');
console.log(obj.randomNumber);
console.log(obj.randomNumber);
console.log(obj.randomNumber);
.as-console-wrapper { max-height: 100% !important; top: 0; }
The concept of combining multiple prototypes into a new one is often coined "mixin". Here are some related Q&A: