How inheritance works in JavaScript (Without the Magic)
JavaScript ES6 has come with some syntactic sugar features, notably classes, which make the language more “object-oriented” and reduce the verbosity of the code we have to write to some extent. However, classes have somewhat abstracted the underlying behavior of prototypes and inheritance, which differs from OO languages like C# or Java.
Therefore, to become a truly competent developer, we can’t just use those features without fully grasping their inner workings. In this article, we’ll explore how inheritance actually works in JavaScript via some simple examples.
function Animal(name, age) {
this.name = name;
this.age = age;
}
Animal.prototype.eat = function() {
console.log("Eating...");
}
function Bird(name, age, canFly = true) {
Animal.call(this, name, age);
this.canFly = canFly;
}
Bird.prototype = Object.create(Animal.prototype); // inherit the prototype chain as well
Bird.prototype.fly = function() {
if (this.canFly)
console.log("Flying...");
else
console.log("Cannot fly");
}
Bird.prototype.constructor = Bird; // override the constructor from Animal's to Bird's
const bird = new Bird("Cuckoo", 3, true);In the snippet above:
- Instances created by
Animalfunction has two propertiesnameandage, and has a methodeatdefined inside its prototype object. Birdinstance receivesAnimal's instance properties via constructor chaining, and inherit its prototype methods through the prototype chain.
There are some caveats you need to bear in mind:
We need to define additional methods on Bird.prototype after assigning it to Animal.prototype
We don’t directly define the method eat inside the Animal function and declare it on the prototype instead. This has some benefits:
- Only one
eatmethod will be reused in memory since objects instantiated from a function that inherits from its parent will use the sameeatfunction in the prototype chain. This leads to better performance, especially with many objects. - Better leverage of JS’s prototype inheritance patterns (as we’ll see later).
In JS, inheritance works via what’s known as the prototype chain, which we’ll explore in the following section.
Prototype chain
Let’s look at how the code example above works under the hood.
If you try logging the bird object in the browser, you’ll see this:
In the image above, along with the attributes we’ve defined, there’s the [[Prototype]], which contains the properties and methods that the object inherits from its ancestors and can access inside the prototype chain.
Get Alpha Tran’s stories in your inbox
Join Medium for free to get updates from this writer.
The prototype chain of this bird object is:
bird
↓
Bird.prototype
↓
Animal.prototype
↓
...
↓
Object.prototype
↓
nullWhen a property is accessed or a method is invoked, under the hood:
- The engine attempts to look up in the chain above by following the internal
[[Prototype]]reference (which we can inspect usingObject.getPrototypeOf). - When it reaches
null, the lookup returnsundefined. Attempting to call it as a function throws an error likebird.x is not a function.
What about bird.toString()? Likewise, the method toString actually exists in the prototype of Object, which returns [object Object].
What if we override the toString method on Bird prototype?
Bird.prototype.toString = function() {
return this.name + " is " + this.age + " years old";
}In this case, the first toString method found in the chain is in Bird.prototype. Therefore, it should return “Cuckoo is 3 years old”.
If you print the bird object again, you’ll see the method toString in Bird.prototype:
This is how JS enables polymorphism under the hood!
In a nutshell, inheritance in JavaScript is delegation, not copying. Objects delegate property lookup to other objects via the prototype chain.
Once again, to end this article, classes are just syntactic sugar for what we’ve done manually.
class Animal {
constructor(name, age) {
this.name = name;
this.age = age;
}
eat() {
console.log("Eating...");
}
}
class Bird extends Animal {
constructor(name, age, canFly = true) {
super(name, age);
this.canFly = canFly;
}
fly() {
console.log("Flying...");
}
toString() {
return this.name + " is " + this.age + " years old";
}
}If you’ve made it this far, congratulations! Understanding how inheritance works and prototyping chains in JavaScript isn’t an easy thing for beginners.
Thank you for reading my article! Happy coding :)





