The prototypal inheritance is how, in JavaScript, we share reusable properties and methods throughout objects. In this article, we’ll see how it works.
Say we represented a user with the following object:
let user = {
name: 'John Snow',
getName() {
return this.name;
}
};
We want an admin user with the same shape and feature with an additional isAdmin
property set to true
. Here’s how prototypal inheritance helps us achieve this without repeating ourselves:
let user = {
// ...
};
let admin = Object.create(user);
admin.isAdmin = true; // "isAdmin" is a new property set only in `admin`
admin.name = 'Daenerys Targaryen'; // "name" is an inherited property that is overwritten
console.log(admin.getName()); // Daenerys Targaryen ("getName" is also inherited from `user`)
By calling Object.create(user)
we’ve made user
the prototype of admin
. It means that we have access to all the properties and methods of user
through admin
.
The Prototype Chain
When we call user.getName()
and admin.geName()
, two different things happen:
- We find
getName
directly inuser
, but not inadmin
. - For
admin
, we implicitly go one step further in its prototype (user
) to find the elusivegetName
.
By going one step further to reach a prototype, we’re walking through the prototype chain. This chain can expand if we introduce a root user, for example:
let user = {
//...
};
let admin = Object.create(user);
//...
let root = Object.create(admin);
root.isRoot = true;
root.name = 'Night King';
console.log(root.getName()); // Night King (inherited from `user`)
console.log(root.isAdmin); // true (inherited from `admin`)
console.log(root.isRoot); // true (proper to `root`)
Here, when we lookup root.getName
, we walk through the prototype chain until the user
prototype.
The ES6 class
An ES6 class and its instances also use a prototype behind the scene. When we define a constructor, it stores all of its properties and methods in the prototype
property. Let’s define a User
class and some instances to see that in action:
class User {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
const user1 = new User('Joe');
const user2 = new User('Kim');
console.log(User.prototype === user1.__proto__); // true
console.log(user1.__proto__ === user2.__proto__); // true
As you can see, the constructor and the instances all share the same prototype. We access the prototype using prototype
in the constructor and __proto__
in the instances.
Wrap up
Sharing properties and methods between objects is important because it allows us to make our code reusable and well-organized. We achieve this in JavaScript through a concept called prototypal inheritance. A prototype is an object referenced by other objects seeking the same properties and methods. A prototype can also have a prototype which in turn has a prototype, and so on, forming the prototype chain. We walk through this chain when a property or method is not found directly in a given object.