Multiple Inheritance in Javascript

Notice

Javascript does not support multiple inheritance, so the given method will break the instanceof operator! Javascript checks inheritance by traversing the linked list prototype.__proto__ for occurences of the requested prototype. This means that one prototype can only contain one reference to another prototype and in effect only inherit from one prototype. By discarding support for the instanceof operator, multiple inheritance can be simulated.

A support function is available that provide similar functionality as the instanceof operator.

Description and implementation details

The function responsible for applying inheritance between classes works by copying all missing attributes/methods from parent class(es) to the inheritance class. In addition, metadata in the class constructors allows for isInstanceOf() comparisons to to made on objects.

/**
 * Check if object is instance of given class.
 *
 * @param {Object} object Object to check inheritance of.
 * @param {Function} classConstructor Constructor function to check inheritance against.
 * @return Boolean indicating success of comparison.
 * @type {Boolean}
 */
function isInstanceOf(object, classConstructor) {
  // Check for class metadata
  if (object.constructor.meta === undefined || classConstructor.meta === undefined) {
    return object instanceof classConstructor; // Use standard inheritance test
  }
 
  // Use inheritance metadata to perform instanceof comparison
  return object.constructor.meta.fromClasses[classConstructor.meta.index] !== undefined;
}
 
/**
 * Applies inheritance to a class (first argument) from classes (second, third, and
 * so on, arguments).
 *
 * Notes:
 *  - This WILL BREAK the instanceof operator! Use the isInstanceOf() function instead.
 *  - Parent classes must be fully declared before calling this function.
 *  - Multiple classes will be copied in sequence.
 *  - Properties that already exists in class will not be copied.
 */
function applyInheritance() {
  // Validate arguments
  if (arguments.length < 2) {
    throw new Error("No inheritance classes given");
  }
 
  var toClass = arguments[0];
  var fromClasses = Array.prototype.slice.call(arguments, 1, arguments.length);
 
  // Check if class referencer has been created
  if (applyInheritance.allClasses === undefined) {
    applyInheritance.allClasses = [];
  }
 
  // Check for inheritance metadata in toClass
  if (toClass.meta === undefined) {
    toClass.meta = {
      index: applyInheritance.allClasses.length,
      fromClasses : [],
      toClasses: []
    };
    toClass.meta.fromClasses[toClass.meta.index] = true; // class links to itself
    applyInheritance.allClasses.push(toClass);
  }
 
  // Apply inheritance fromClasses
  var fromClass = null;
  for (var i = 0; i < fromClasses.length; i++) {
    fromClass = fromClasses[i];
 
    // Check for inheritance metadata in fromClass
    if (fromClass.meta === undefined) {
      fromClass.meta = {
        index: applyInheritance.allClasses.length,
        fromClasses: [],
        toClasses: []
      };
      fromClasses[i].meta.fromClasses[fromClass.meta.index] = true; // class links to itself
      applyInheritance.allClasses.push(fromClass);
    }
 
    // Link toClass and fromClass
    toClass.meta.fromClasses[fromClass.meta.index] = true;
    fromClass.meta.toClasses[toClass.meta.index] = true;
 
    // Copy prototype fromClass toClass
    for (var property in fromClass.prototype) {
      if (toClass.prototype.hasOwnProperty(property) === false) {
        // Copy missing property from the parent class to the inheritance class
        toClass.prototype[property] = fromClass.prototype[property];
      }
    }
  }
}

Multiple inheritance example:

function Animal(name) {
   // Attributes
   this.name = name;
}
 
Animal.prototype.makeSound = function (soundOutput) {
   soundOutput.playSound("Unknown");
};
 
Animal.prototype.getHierarchy = function () {
   return "Animal";
};
 
function Petable(personality) {
   // Attributes
   this.personality = personality;
}
 
Petable.prototype.isCompatible = function (personality) {
   return this.personality == personality;
};
 
function Dog(name, personality, breed) {
   // Call parent constructors
   Animal.call(this, name);
   Petable.call(this, personality);
 
   // Attributes
   this.breed = breed;
}
// Apply inheritance to Dog from Animal and Petable
applyInheritance(Dog, Animal, Petable);

An example of how to override a method of a parent class:

Dog.prototype.makeSound = function (soundOutput) {
   soundOutput.playSound("Bark");
};

An example of how an overidden method can delegate to it’s parent method:

Dog.prototype.getHierarchy = function () {
   // returns "Animal.Dog"
   return Animal.prototype.getHierarchy.apply(this, arguments) + ".Dog";
};

An example of how to call a parent method:

Dog.prototype.isOwner = function (name, personality) {
   if (this.name != name) {
      return false;
   }
 
   return Petable.prototype.isCompatible.call(this, personality);
};

Notes on calling function objects

References

6 Replies to “Multiple Inheritance in Javascript”

  1. Your code does not cause inheritance. In inheritance future changes in parent methods are reflected in all instances.


    // All your above code here

    var fido = new Dog()

    alert(fido.makeSound) // function (soundOutput) { soundOutput.playSound("Unknown"); }

    Animal.prototype.makeSound = function (soundOutput) {
    soundOutput.playSound("Bark");
    };

    alert( fido.makeSound ) // function (soundOutput) { soundOutput.playSound("Unknown"); }

    Changing the makeSound method was not reflected in the fido instance therefore there is no inheritance and therefore no multiple inheritance either.

  2. You’re right that this only copies the methods/attributes when constructing classes. It assumes everything is defined on initialization.

  3. Thanks for the link to the implementation. Looks like a nice and neat way to handle inheritance ala traits. The good performance doesn’t hurt either. :)

Leave a Reply

Your email address will not be published. Required fields are marked *