javascriptprototypemultiple-inheritance

Multiple inheritance/prototypes in JavaScript


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?


Solution

  • 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:

    There are more traps available, which I don't use

    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"