Multiple inheritance/prototypes in JavaScript
Multiple inheritance can be achieved in ECMAScript 6 by using Proxy objects.
Implementation
function getDesc (obj, prop) {
var desc = Object.getOwnPropertyDescriptor(obj, prop);
return desc || (obj=Object.getPrototypeOf(obj) ? getDesc(obj, prop) : void 0);
}
function multiInherit (...protos) {
return Object.create(new Proxy(Object.create(null), {
has: (target, prop) => protos.some(obj => prop in obj),
get (target, prop, receiver) {
var obj = protos.find(obj => prop in obj);
return obj ? Reflect.get(obj, prop, receiver) : void 0;
},
set (target, prop, value, receiver) {
var obj = protos.find(obj => prop in obj);
return Reflect.set(obj || Object.create(null), prop, value, receiver);
},
*enumerate (target) { yield* this.ownKeys(target); },
ownKeys(target) {
var hash = Object.create(null);
for(var obj of protos) for(var p in obj) if(!hash[p]) hash[p] = true;
return Object.getOwnPropertyNames(hash);
},
getOwnPropertyDescriptor(target, prop) {
var obj = protos.find(obj => prop in obj);
var desc = obj ? getDesc(obj, prop) : void 0;
if(desc) desc.configurable = true;
return desc;
},
preventExtensions: (target) => false,
defineProperty: (target, prop, desc) => false,
}));
}
Explanation
A proxy object consists of a target object and some traps, which define custom behavior for fundamental operations.
When creating an object which inherits from another one, we use Object.create(obj)
. But in this case we want multiple inheritance, so instead of obj
I use a proxy that will redirect fundamental operations to the appropriate object.
I use these traps:
- The
has
trap is a trap for thein
operator. I usesome
to check if at least one prototype contains the property. - The
get
trap is a trap for getting property values. I usefind
to find the first prototype which contains that property, and I return the value, or call the getter on the appropriate receiver. This is handled byReflect.get
. If no prototype contains the property, I returnundefined
. - The
set
trap is a trap for setting property values. I usefind
to find the first prototype which contains that property, and I call its setter on the appropriate receiver. If there is no setter or no prototype contains the property, the value is defined on the appropriate receiver. This is handled byReflect.set
. - The
enumerate
trap is a trap forfor...in
loops. I iterate the enumerable properties from the first prototype, then from the second, and so on. Once a property has been iterated, I store it in a hash table to avoid iterating it again.
Warning: This trap has been removed in ES7 draft and is deprecated in browsers. - The
ownKeys
trap is a trap forObject.getOwnPropertyNames()
. Since ES7,for...in
loops keep calling [[GetPrototypeOf]] and getting the own properties of each one. So in order to make it iterate the properties of all prototypes, I use this trap to make all enumerable inherited properties appear like own properties. - The
getOwnPropertyDescriptor
trap is a trap forObject.getOwnPropertyDescriptor()
. Making all enumerable properties appear like own properties in theownKeys
trap is not enough,for...in
loops will get the descriptor to check if they are enumerable. So I usefind
to find the first prototype which contains that property, and I iterate its prototypical chain until I find the property owner, and I return its descriptor. If no prototype contains the property, I returnundefined
. The descriptor is modified to make it configurable, otherwise we could break some proxy invariants. - The
preventExtensions
anddefineProperty
traps are only included to prevent these operations from modifying the proxy target. Otherwise we could end up breaking some proxy invariants.
There are more traps available, which I don't use
- The
getPrototypeOf
trap could be added, but there is no proper way to return the multiple prototypes. This impliesinstanceof
won't work neither. Therefore, I let it get the prototype of the target, which initially is null. - The
setPrototypeOf
trap could be added and accept an array of objects, which would replace the prototypes. This is left as an exercice for the reader. Here I just let it modify the prototype of the target, which is not much useful because no trap uses the target. - The
deleteProperty
trap is a trap for deleting own properties. The proxy represents the inheritance, so this wouldn't make much sense. I let it attempt the deletion on the target, which should have no property anyway. - The
isExtensible
trap is a trap for getting the extensibility. Not much useful, given that an invariant forces it to return the same extensibility as the target. So I just let it redirect the operation to the target, which will be extensible. - The
apply
andconstruct
traps are traps for calling or instantiating. They are only useful when the target is a function or a constructor.
Example
// Creating objects
var o1, o2, o3,
obj = multiInherit(o1={a:1}, o2={b:2}, o3={a:3, b:3});
// Checking property existences
'a' in obj; // true (inherited from o1)
'b' in obj; // true (inherited from o2)
'c' in obj; // false (not found)
// Setting properties
obj.c = 3;
// Reading properties
obj.a; // 1 (inherited from o1)
obj.b; // 2 (inherited from o2)
obj.c; // 3 (own property)
obj.d; // undefined (not found)
// The inheritance is "live"
obj.a; // 1 (inherited from o1)
delete o1.a;
obj.a; // 3 (inherited from o3)
// Property enumeration
for(var p in obj) p; // "c", "b", "a"
Update (2019): The original post is getting pretty outdated. This article (now internet archive link, since domain went away) and its associated GitHub library are a good modern approach.
Original post: Multiple inheritance [edit, not proper inheritance of type, but of properties; mixins] in Javascript is pretty straightforward if you use constructed prototypes rather than generic-object ones. Here are two parent classes to inherit from:
function FoodPrototype() {
this.eat = function () {
console.log("Eating", this.name);
};
}
function Food(name) {
this.name = name;
}
Food.prototype = new FoodPrototype();
function PlantPrototype() {
this.grow = function () {
console.log("Growing", this.name);
};
}
function Plant(name) {
this.name = name;
}
Plant.prototype = new PlantPrototype();
Note that I have used the same "name" member in each case, which could be a problem if the parents did not agree about how "name" should be handled. But they're compatible (redundant, really) in this case.
Now we just need a class that inherits from both. Inheritance is done by calling the constructor function (without using the new keyword) for the prototypes and the object constructors. First, the prototype has to inherit from the parent prototypes
function FoodPlantPrototype() {
FoodPrototype.call(this);
PlantPrototype.call(this);
// plus a function of its own
this.harvest = function () {
console.log("harvest at", this.maturity);
};
}
And the constructor has to inherit from the parent constructors:
function FoodPlant(name, maturity) {
Food.call(this, name);
Plant.call(this, name);
// plus a property of its own
this.maturity = maturity;
}
FoodPlant.prototype = new FoodPlantPrototype();
Now you can grow, eat, and harvest different instances:
var fp1 = new FoodPlant('Radish', 28);
var fp2 = new FoodPlant('Corn', 90);
fp1.grow();
fp2.grow();
fp1.harvest();
fp1.eat();
fp2.harvest();
fp2.eat();
This one uses Object.create
to make a real prototype chain:
function makeChain(chains) {
var c = Object.prototype;
while(chains.length) {
c = Object.create(c);
$.extend(c, chains.pop()); // some function that does mixin
}
return c;
}
For example:
var obj = makeChain([{a:1}, {a: 2, b: 3}, {c: 4}]);
will return:
a: 1
a: 2
b: 3
c: 4
<Object.prototype stuff>
so that obj.a === 1
, obj.b === 3
, etc.
I like John Resig's implementation of a class structure: http://ejohn.org/blog/simple-javascript-inheritance/
This can be simply extended to something like:
Class.extend = function(prop /*, prop, prop, prop */) {
for( var i=1, l=arguments.length; i<l; i++ ){
prop = $.extend( prop, arguments[i] );
}
// same code
}
which will allow you to pass in multiple objects of which to inherit. You're going to lose instanceOf
capability here, but that's a given if you want multiple inheritance.
my rather convoluted example of the above is available at https://github.com/cwolves/Fetch/blob/master/support/plugins/klass/klass.js
Note that there is some dead code in that file, but it allows multiple inheritance if you want to take a look.
If you want chained inheritance (NOT multiple inheritance, but for most people it's the same thing), it can be accomplished with Class like:
var newClass = Class.extend( cls1 ).extend( cls2 ).extend( cls3 )
which will preserve the original prototype chain, but you'll also have a lot of pointless code running.
Don't get confused with JavaScript framework implementations of multiple inheritance.
All you need to do is use Object.create() to create a new object each time with the specified prototype object and properties, then be sure to change the Object.prototype.constructor each step of the way if you plan on instantiating B
in the future.
To inherit instance properties thisA
and thisB
we use Function.prototype.call() at the end of each object function. This is optional if you only care about inheriting the prototype.
Run the following code somewhere and observe objC
:
function A() {
this.thisA = 4; // objC will contain this property
}
A.prototype.a = 2; // objC will contain this property
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
function B() {
this.thisB = 55; // objC will contain this property
A.call(this);
}
B.prototype.b = 3; // objC will contain this property
C.prototype = Object.create(B.prototype);
C.prototype.constructor = C;
function C() {
this.thisC = 123; // objC will contain this property
B.call(this);
}
C.prototype.c = 2; // objC will contain this property
var objC = new C();
-
B
inherits the prototype fromA
-
C
inherits the prototype fromB
-
objC
is an instance ofC
This is a good explanation of the steps above:
OOP In JavaScript: What You NEED to Know