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.inheritance === undefined || classConstructor.inheritance === undefined) {
return object instanceof classConstructor; // Use standard inheritance test.
}
// Use inheritance metadata to perform instanceof comparison.
return object.constructor.inheritance.fromClasses[classConstructor.inheritance.index] !== undefined;
}
/**
* Applies inheritance to a class (first argument) from specified parent
* 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("Error while calling the function applyInheritance(). Not enough arguments given.")
}
// Check if classList has been created.
if (applyInheritance.classList === undefined) {
applyInheritance.classList = [];
}
// Create inheritance metadata for inheritence class.
if (arguments[0].inheritance === undefined) {
arguments[0].inheritance = {};
arguments[0].inheritance.index = applyInheritance.classList.length;
arguments[0].inheritance.fromClasses = [];
arguments[0].inheritance.fromClasses[arguments[0].inheritance.index] = true; // class links to itself.
arguments[0].inheritance.toClasses = [];
applyInheritance.classList.push(arguments[0]);
}
// Iterate through all parent classes.
for (var i = 1; i < arguments.length; i++) {
// Create inheritance metadata for parent class.
if (arguments[i].inheritance === undefined) {
arguments[i].inheritance = {};
arguments[i].inheritance.index = applyInheritance.classList.length;
arguments[i].inheritance.fromClasses = [];
arguments[i].inheritance.fromClasses[arguments[i].inheritance.index] = true; // class links to itself.
arguments[i].inheritance.toClasses = [];
applyInheritance.classList.push(arguments[i]);
}
// Update inheritance metadata.
arguments[0].inheritance.fromClasses[arguments[i].inheritance.index] = true;
arguments[i].inheritance.toClasses[arguments[0].inheritance.index] = true;
// Iterate through all properties in prototype of parent class.
for (var property in arguments[i].prototype) {
if (arguments[0].prototype.hasOwnProperty(property) === false) {
// Copy missing property from the parent class to the inheritance class.
arguments[0].prototype[property] = arguments[i].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 class constructors.
Animal.call(this, name);
Petable.call(this, personality);
// Attributes
this.breed = breed;
}
applyInheritance(Dog, Animal, Petable); // Applied inheritance to Dog from Animal and 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 () {
return Animal.prototype.getHierarchy.apply(this, arguments) + ".Dog"; // Will return 'Animal.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);
};
About calling function objects:
Two options are available when calling a javascript function object.
- Using the call() method which accepts as arguments a this object followed by one or more arguments to pass on (depending on the number of arguments in the called function).
- Using the apply() method which accepts as arguments a this object followed by an function arguments object.
References