JavaScript — Prototypal Inheritance [Challenges & Caveats]
This article focuses on the major challenges and caveats for proper implementation of JavaScript Prototypal Inheritance.
If you are looking to understand the Basics of JavaScript Prototypal Inheritance, I highly encourage you to please read it here.
Let’s Begin….
Consider PersonClass to be the Parent and instance of it is an object p1.
function PersonClass(name, age) {
this.name = name;
this.age = age;
}PersonClass.prototype.walk = function() {
console.log('Person ' + this.name + ' is walking');
}var p1 = new PersonClass('David', 50);
p1.walk();// Output : Person David is walking
Below, we are creating our Child [EmployeeClass] with its own method code() so that all of its instances can share & use it.
function EmployeeClass(name, age, company, favLang) {
PersonClass.call(this, name, age);
this.company = company;
this.favLang = favLang;
}EmployeeClass.prototype.code = function() {
console.log('Employee ' + this.name + ' is coding in ' + this.favLang);
}
Now to form the inheritance hierarchy so that instances of EmployeeClass can re-use PersonClass method — walk(), it can be achieved in 4 different ways.
1. Directly Linking Child’s prototype object to Parent’s prototype object
EmployeeClass.prototype = PersonClass.prototype;var emp1 = new EmployeeClass('John',25, 'XYZ Corp', 'JavaScript');emp1.walk()
// Output : Person John is walking
This will enable any instance of EmployeeClass to use walk() method inherited from PersonClass but will cause few issues.
Issue 1:
In process inheriting the PersonClass, mistakenly we have overridden the prototype of EmployeeClass. Because of that, EmployeeClass’s own method — code() is no more available.
emp1.code()
// Output : Uncaught TypeError: emp1.code is not a function
Solution:
Be careful with the sequence of attaching/updating the Child class prototype object.
First link the prototype to parent and then attach the required functions on top of it. Sequences of operations on prototype object is really important.
function EmployeeClass(name, age, company, favLang) {
PersonClass.call(this, name, age);
this.company = company;
this.favLang = favLang;
}// First Link
EmployeeClass.prototype = PersonClass.prototype;// Then Attach/Update
EmployeeClass.prototype.code = function() {
console.log('Employee ' + this.name + ' is coding in ' + this.favLang);
}var emp1 = new EmployeeClass('John', 27, 'XYZ Corp', 'JavaScript');emp1.code();
// Output : Employee John is coding in JavaScript
Issue 2:
As we have directly linked our Child class prototype object to Parent.
So, any addition or modification to Child prototype will pollute and affect the Parent’s prototype as both are referencing the same object.
p1.code();
// Output : Employee David is coding in undefined
2. Linking Child prototype object to Parent instance
EmployeeClass.prototype = new PersonClass();
This will work perfectly fine. But in this scenario, we are creating new instance of PersonClass and assigning it to EmployeeClass prototype.
Issue :
It is not the optimal solution as we are just wasting the memory by Initiating the Parent class to create a new object.
emp1 prototype is now having all the properties of PersonClass instance like age, name and __proto__ of that is actually pointing to the required prototype object consisting walk() method.
3. Linking Child prototype object to new instance of Parent Prototype using Object.assign()
EmployeeClass.prototype = Object.assign({}, PersonClass.prototype);
This looks good as the Child class instance __proto__ is referring to a newly created object and code() is not leaked to Parent prototype.
The Object.assign()
method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object.
Issue :
The method from ParentClass i.e. walk() is also at ChildClass instance i.e. emp1 __proto__ instead of emp1.__proto__.__proto__
Ideally, only method — code() should have been present on __proto__ of ChildClass instance.
4. Using Object.create() and Linking Child’s prototype object to Parent instance
EmployeeClass.prototype = Object.create(PersonClass.prototype);
The Object.create()
method creates a new object, using an existing object as the prototype of the newly created object.
So, this looks like a perfect solution as ChildClass instance __proto__ is only having code() method and __proto__ of that object is pointing to ParentClass instance’s __proto__ or ParentClass’s prototype.
So, finally we have implemented the Prototype chain in a right way using Object.create() and understood the caveat of different ways of implementations.
Bonus Section 🎁🎁
All prototype chain should have correct constructor functions.
This is NOT a mandatory requirement but always good to have as it will be helpful to handle few of the edge case scenarios.
Example 1
Lets assume that we have a requirement to create a duplicate object of the instance variable of Parent class and add few extra properties to enhance it.
function PersonClass(name, age) {
this.name = name;
this.age = age;
}PersonClass.prototype.walk = function() {
console.log('Person ' + this.name + ' is walking');
}PersonClass.prototype.createDuplicate = function() {
return new this.constructor(this.name, this.age);
};var p1 = new PersonClass('David', 50);
console.log('p1: ', p1);var p1_ehanced = p1.createDuplicate();
p1_ehanced.mobile = '111-222-3333';
console.log('p1_ehanced: ', p1_ehanced);
In this example, we added one new property “mobile” to enhance person p1 info. We were able to do this by initiating the constructor with new keyword inside createDuplicate().
But same concept will break if we try replicating across our EmployeeClass.
function EmployeeClass(name, age, company, favLang) {
PersonClass.call(this, name, age);
this.company = company;
this.favLang = favLang;
}EmployeeClass.prototype = Object.create(PersonClass.prototype);EmployeeClass.prototype.code = function() {
console.log('Employee ' + this.name + ' is coding in ' + this.favLang);
}EmployeeClass.prototype.createDuplicate = function() {
return new this.constructor(this.name, this.age, this.company, this.favLang);
};var emp1 = new EmployeeClass('John', 25, 'XYZ Corp', 'JavaScript');
console.log('emp1: ', emp1);var emp1_enhanced = emp1.createDuplicate();
console.log('emp1_enhanced: ', emp1_enhanced);
Output of emp1_enhanced is NOT an EmployeeClass instance, rather PersonClass instance.
Solution :
By default all the object’s __proto__ will have constructor pointing to self Function Constructor like in case of PersonClass unless
- You create an object using using Object.create(null)
var foo = Object.create(null);
console.log(foo.__proto__)// Output : undefined
2. Or you reach to the Global Object prototype in inheritance hierarchy
var foo = Object.create({});
console.log(foo.__proto__.__proto__.__proto__)// Output : null
In our case, we broke this linkage while creating the inheritance and that can be easily fixed by putting it back on prototype object.
EmployeeClass.prototype = Object.create(PersonClass.prototype); // Put back the constructor function on prototype object.
EmployeeClass.prototype.constructor = EmployeeClass;
We can also achieve the same outcome as part of Object.create() with the following code i.e. by passing second argument to Object.create() which will be the properties of the newly created object.
It is supposed to contain property descriptors, not just values.
EmployeeClass.prototype = Object.create(PersonClass.prototype, {
constructor: {
value: EmployeeClass
}
});
I hope at this point you would have got clear understanding of “How to properly implement Inheritance Hierarchy in JavaScript”. 🎯🎯
Kindly comment below to get any clarification on the discussed approaches or if you want to share some better solutions.
🏁🏁 Happy coding…