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
call(context: object, arg1, arg2 .. arg3)– calls the function using it’s formal argumentsapply(context: object, arguments: array)– call the function using the arguments object which later is expanded to the formal arguments
References
- Object Hierarchy and Inheritance in JavaScript
- Harry Fuecks’ blog entry on Inheritance in Javascript
- Josh Davis’ blog entry on Prototype & Object Oriented JavaScript
- Adam’s blog entry on Javascript Inheritance
- Mozilla.org: Core JavaScript 1.5 Reference – Function:call
- Mozilla.org: Core JavaScript 1.5 Reference – Function:apply
- Mozilla.org: Core JavaScript 1.5 Reference – Function object