Implementing Inheritance in JavaScript Using Prototype Chains
In object-oriented programming, inheritance allows new types to acquire properties and methods from existing ones. JavaScript supports implementation inheritance primari through prototype chains, as it lacks interface inheritance due to functions not having signatures.
Prototype Chain Mechanism
A prototype chain enables inheritance by linking objects through their prototypes. Each constructor has a prototype object, which points back to the constructor, and instances have an internal link to this prototype. When a prototype is set to an instance of another type, it creates a chain where properties and methods can be inherited upward.
function ParentClass() {
this.parentFlag = true;
}
ParentClass.prototype.fetchParentFlag = function() {
return this.parentFlag;
};
function ChildClass() {
this.childFlag = false;
}
// Inherit from ParentClass
ChildClass.prototype = new ParentClass();
ChildClass.prototype.fetchChildFlag = function() {
return this.childFlag;
};
let obj = new ChildClass();
console.log(obj.fetchParentFlag()); // true
In this example, ChildClass inherits from ParentClass by assigning a new ParentClass instance to ChildClass.prototype. This overrides the original prototype, allowing ChildClass instances to access methods from ParentClass.prototype. The fetchParentFlag method resides on ParentClass.prototype, while parentFlag is an instance property on ChildClass.prototype.
Property lookup follows the chain: when accessing a property on an instance, JavaScript searches the instance, then its prototype, and continues up the chain. For obj.fetchParentFlag(), it checks obj, ChildClass.prototype, and ParentClass.prototype.
Determining Inheritance Relationships
Use instanceof to check if an instance's prototype chain contains a constructor:
console.log(obj instanceof ParentClass); // true
console.log(obj instanceof ChildClass); // true
Alternatively, isPrototypeOf() verifies if a prototype is in the chain:
console.log(ParentClass.prototype.isPrototypeOf(obj)); // true
console.log(ChildClass.prototype.isPrototypeOf(obj)); // true
Modifying Methods in Inheritance
To override or add methods, define them after setting up the prototype chain to avoid breaking it.
function ParentClass() {
this.parentFlag = true;
}
ParentClass.prototype.fetchParentFlag = function() {
return this.parentFlag;
};
function ChildClass() {
this.childFlag = false;
}
ChildClass.prototype = new ParentClass();
// Add new method
ChildClass.prototype.fetchChildFlag = function() {
return this.childFlag;
};
// Override inherited method
ChildClass.prototype.fetchParentFlag = function() {
return false;
};
let obj = new ChildClass();
console.log(obj.fetchParentFlag()); // false
Using object literals to redefine the prototype can disrupt the chain, as it replaces the prototype entirely.
function ParentClass() {
this.parentFlag = true;
}
ParentClass.prototype.fetchParentFlag = function() {
return this.parentFlag;
};
function ChildClass() {
this.childFlag = false;
}
ChildClass.prototype = new ParentClass();
// This breaks the inheritance
ChildClass.prototype = {
fetchChildFlag() {
return this.childFlag;
},
anotherMethod() {
return false;
}
};
let obj = new ChildClass();
console.log(obj.fetchParentFlag()); // Error: method not found
Limitations of Prototype Chains
A key issue arises with reference values in prototypes, as they are shared across all instances.
function ParentClass() {
this.items = ["apple", "banana", "cherry"];
}
function ChildClass() {}
ChildClass.prototype = new ParentClass();
let obj1 = new ChildClass();
obj1.items.push("date");
console.log(obj1.items); // ["apple", "banana", "cherry", "date"]
let obj2 = new ChildClass();
console.log(obj2.items); // ["apple", "banana", "cherry", "date"]
Here, items is a reference value on ParentClass.prototype, so modifications affect all ChildClass instances. Additionally, prototype chains do not allow passing arguments to parent constructors during child instantiasion, limiting flexibility. Due to these drawbacks, prototype chains are often combined with other patterns for practical use.