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]; } } } }
Code language: JavaScript (javascript)

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);
Code language: JavaScript (javascript)

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

Dog.prototype.makeSound = function (soundOutput) { soundOutput.playSound("Bark"); };
Code language: JavaScript (javascript)

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"; };
Code language: JavaScript (javascript)

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); };
Code language: JavaScript (javascript)

Notes on calling function objects

References

6 comments

  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 comment

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