Monday, May 20, 2024
 Popular · Latest · Hot · Upcoming
72
rated 0 times [  77] [ 5]  / answers: 1 / hits: 77152  / 13 Years ago, mon, february 6, 2012, 12:00:00

I've come to a point where I need to have some sort of rudimentary multiple inheritance happening in JavaScript. (I'm not here to discuss whether this is a good idea or not, so please kindly keep those comments to yourself.)



I just want to know if anyone's attempted this with any (or not) success, and how they went about it.



To boil it down, what I really need is to be able to have an object capable of inheriting a property from more than one prototype chain (i.e. each prototype could have its own proper chain), but in a given order of precedence (it will search the chains in order for the first definition).



To demonstrate how this is theoretically possible, it could be achieved by attaching the secondary chain onto the end of the primary chain, but this would affect all instances of any of those previous prototypes and that's not what I want.



Thoughts?


More From » prototype

 Answers
34

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 the in operator. I use some to check if at least one prototype contains the property.

  • The get trap is a trap for getting property values. I use find 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 by Reflect.get. If no prototype contains the property, I return undefined.

  • The set trap is a trap for setting property values. I use find 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 by Reflect.set.

  • The enumerate trap is a trap for for...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 for Object.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 for Object.getOwnPropertyDescriptor(). Making all enumerable properties appear like own properties in the ownKeys trap is not enough, for...in loops will get the descriptor to check if they are enumerable. So I use find 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 return undefined. The descriptor is modified to make it configurable, otherwise we could break some proxy invariants.

  • The preventExtensions and defineProperty 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 implies instanceof 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 and construct 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

[#87621] Saturday, February 4, 2012, 13 Years  [reply] [flag answer]
Only authorized users can answer the question. Please sign in first, or register a free account.
cruzs

Total Points: 710
Total Questions: 113
Total Answers: 100

Location: Nepal
Member since Sat, Jul 18, 2020
4 Years ago
cruzs questions
Thu, Nov 26, 20, 00:00, 4 Years ago
Wed, Oct 28, 20, 00:00, 4 Years ago
Wed, Aug 19, 20, 00:00, 4 Years ago
Sun, Aug 2, 20, 00:00, 4 Years ago
;