TL;DR: In this post, we will look at prototypes and how to use them for inheritance in JavaScript. We will also see how the prototypical approach is different from class-based inheritance.
Inheritance, a prominent feature of a programming language, emerged with the introduction of object-oriented programming languages. Most of these languages were class-based languages. Here, class is like a plan or blueprint and objects are its manifestation. Meaning, in order to create an object, first we have to create a class. Then we can create any number of objects from one class.
Imagine, we have a class that represents a smartphone. This class has features like capturing images, GPS, etc, like any other smartphone. Here’s an example of how to create such a class and an object in C++ :
We created a class named SmartPhone
and it has a method named capturePictures
, to capture images.
Let’s imagine, we need an iPhone class, that would capture images along with some special features like a face ID scan. Here are two possible solutions:
captureImages
feature along with other common smartphone features, plus iPhone specific features into a new class. But this approach takes more time, effort and can introduce more bugs.SmartPhone
class. This is where inheritance comes into play. It’s a way to reuse features from other classes/objects.Here is how we can inherit capturePictures
method from the SmartPhone
class, in our new Iphone
class, in C++ :
Above is a trivial example of inheritance. However, it shows that inheritance allows us to reuse code in a way that the resulting program is less error-prone and takes less time to develop.
Here are some important things to know about classes :
It’s worth to note that, class in itself is not doing anything. Until you create an object from a class, no work is actually done. We will see why it’s different from JavaScript.
In JavaScript, all objects have a special internal property which is basically a reference to another object. This reference depends upon how the object is created. In ECMAScript/JavaScript specification, it is denoted as [[Prototype]]
.
Since [[Prototype]]
is linked to an object, that object has its own [[Prototype]]
reference. This is how a chain is built (it’s known as the prototype chain).
This chain of
[[Prototype]]
is the building-block of inheritance in JavaScript.
__proto__
objectTo access the object’s [[Prototype]]
, most of the browsers provide a __proto__
property.
This is how we can access it:
// obj is an actual object obj.__proto__
It’s important to note that, this property is not a part of the ECMAScript standard. It is a de-facto implementation by the browsers.
Apart from the __proto__
property, there is a standard way to access the [[Prototype]]
.
Here is how we can access the [[Prototype]]
of an object:
Object.getPrototypeOf(obj);
There is a similar method to set the [[Prototype]]
of an object. This is how we do it:
Object.setPrototypeOf(obj, prototype);
[[Prototype]]
and .prototype
propertyWe have now discussed [[Prototype]]
. It’s nothing but a standard notation to designate the prototype of an object. Many developers get it confused with .prototype property, which is an entirely different thing.
Let’s explore the .prototype
property.
In JavaScript, there are many ways of creating an object. One way is using a constructor function, by calling it using the new
keyword like this:
When you console.log the phone
object, you will see an object with __proto__
property, like this:
Now, if we want to have some methods on the phone object, we can use .prototype
property on the function, as follows:
When we create the phone object again, we would see the following in the console.log
:
We can see the isAndroid()
method in the object’s [[Prototype]]
.
In short, the .prototype
property is basically like a blueprint for the [[Prototype]]
object created by the given constructor function. Anything that you declare in the .prototype
property/object will pop up in object’s [[Prototype]]
.
As a matter of fact, if you compare the SmartPhone.prototype
to the phone’s [[Prototype]]
, you will see that they are the same:
console.log(Object.getPrototypeOf(phone) === SmartPhone.prototype); // true
It’s worth noting that, we can also create methods inside the constructor function. Instead, we did it using the function’s prototype. There’s a good reason to do so.
Let’s take a look at the following example:
The problem with this approach is when we initiate a new object. All the instances get their own copy of methodA
. On the contrary, when we create it on function’s prototype, all instances of the object share just one copy of the method. Which is more efficient.
When we access a property either to get it, the following happens:
[[Prototype]]
[[Prototype]]
of [[Prototype]]
. This chain ends when either the property is found or there is no [[Prototype]]
left, which means that we have reached the end of the prototype chainWhen we set/create a property, JavaScript always set it on the object itself. Even if the same property exists on the [[Prototype]]
chain. Here is an example:
function MyObject() {} MyObject.prototype.propA = 10; // creating a property on the prototype let myObject = new MyObject(); console.log(myObject.propA); // property on the [[Prototype]] // 10 myObject.propA = 20; // property on the object console.log(myObject.propA); // 20
In the above example, we created a constructor function, which has a property propA
on it’s [[Prototype]]
. When we try to access it for the read operation, we see the value in the console. But when we try to set the same property on the object itself; JavaScript creates a new property on the object with the given value. Now if we want to access the property on the [[Prototype]]
directly, we can’t. It’s called the shadowing of property.
It’s also worth noting that the end of a normal object’s [[Prototype]]
chain is built-in Object.prototype
. That’s the reason why most of the object shares many methods like toString()
. Because they are actually defined on Object.prototype
.
In JavaScript, there is just prototypical inheritance. No matter how we create an object. But still, there are subtle differences, that we should take a look at.
The easiest way to create an object in JavaScript is by using an object literal. This is how we do it:
let obj = {};
If we log the obj in the browser’s console, we will see the following:
So basically, all the objects created with literal notation inherit properties from Object.prototype
.
It’s also worth noting that __proto__
object has reference to the constructor function, from which it’s created. In this case, the constructor
property points to Object
constructor.
Another, not-so-common way of creating an object is using Object
constructor. JavaScript provides a built-in constructor method named Object
to create Objects.
Here’s how we use it:
let obj = new Object();
This approach results in the same object as object literal notation. It inherits properties from Object.prototype
. Since we use Object
as a constructor function.
With this helper method, we can create an object with another object as it’s [[Prototype]]
like this:
This is one of the simplest ways to use inheritance in JavaScript.
Any guess how we can make an object
without any [[Prototype]]
reference?
Similar to how we have the object constructor function provided by JavaScript runtime. We can also create our own constructor, to create an object which suits our needs as we can see here:
function SmartPhone(os) { this.os = os; } SmartPhone.prototype.isAndroid = function() { return this.os === 'Android'; }; SmartPhone.prototype.isIOS = function() { return this.os === 'iOS'; };
Now, we want to create an iPhone class, which should have 'iOS'
as it’s OS. It should also have the faceIDScan
method.
First, we have to create an Iphone
constructor function and inside it, we should call the SmartPhone
constructor, like this:
function Iphone() { SmartPhone.call(this, 'iOS'); }
This will set the this.os
property to 'iOS'
in the Iphone
constructor function.
The reason why we called SmartPhone.call
method is because we need to change the value of this
to refer to Iphone
. It would be similar to calling the parent’s constructor in an object-oriented world.
The next thing is, we have to inherit methods from SmartPhone
constructor. We can use our Object.create
friend here, as follows:
Iphone.prototype = Object.create(SmartPhone.prototype);
Now we can add methods for Iphone
, using .prototype
as follows:
Iphone.prototype.faceIDScan = function() {};
Finally, we can create an object using Iphone
as follows:
let x = new Iphone(); // calling inherited method console.log(x.isIOS()): // true
With the ES6, this whole ordeal is very simple. We can create classes (they are not the same as classes in C++ or other any class-based language, just a syntactical sugar on top of prototypical inheritance) and derive new classes from other classes.
Here is how we create a class in ES6:
class SmartPhone { constructor(os) { this.os = os; } isAndroid() { return this.os === 'Android'; } isIos() { return this.os === 'iOS'; } };
Now we can create a new class which is derived from SmartPhone
, like this :
class Iphone extends SmartPhone { constructor() { super.call('iOS'); } faceIDScan() {} }
Instead of calling SmartPhone.call
, we are calling super.call
. But internally, the JavaScript engine is doing this for us automatically.
Finally, we can create an object using Iphone
as follows:
let x = new Iphone(); x.faceIDScan(); // calling inherited method console.log(x.isIos()): // true
This ES6 example is the same as the previous constructor method example. But it’s much cleaner to read and understand.
Let’s summarize what we have learned so far:
[[Prototype]]
is just a fancy way of referring to an object’s prototype. They’re both the same thing__proto__
property or Object.getPrototypeOf
method[[Prototype]]
which is created using the new
keywordI hope this blog post was useful. To learn more about inheritance in JavaScript take a look at the article on MDN.
Debugging code is always a tedious task. But the more you understand your errors, the easier it is to fix them.
LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to see exactly what the user did that led to an error.
LogRocket records console logs, page load times, stack traces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]