DEV Community

Cover image for Understanding Prototypal Inheritance In JavaScript
Lawrence Eagles
Lawrence Eagles

Posted on • Edited on

Prototypical Inheritance

Understanding Prototypal Inheritance In JavaScript

Table of Contents

  1. What Is Object-oriented Programming (OOP)
  2. Classical vs Prototypal Inheritance
  3. The Prototype Object And The Prototype Chain
  4. Exposing this Keyword in JavaScript
  5. JavaScript Base Object, The Prototype Of All Prototypes
  6. The Power Of Prototypal Inheritance
  7. Closing Thoughts

1. What Is Object-Oriented Programming (OOP)

Object-oriented programming is a programming paradigm that involves organizing codes into object definitions. These are sometimes called classes.

In object-oriented programming, we use objects to model real-world things that we want to represent inside our programs. These objects can contain (encapsulate) related information which is the properties and methods (functions) stored in the objects. These are often the properties and behaviours of the real-life object we are modeling.

Classical vs Prototypal Inheritance

JavaScript is a very unique, beautiful, sometimes weird, and incredibly powerful programming language. Object-oriented JavaScript uses prototypal inheritance. Here we find JavaScript making popular a concept that is in many ways better than what is already in use in other programming languages like Java, C#, and C++ (Classical inheritance).

The classical inheritance or class bases inheritance involves writing classes; these are like blueprints of objects to be created. Classes can inherit from classes and even create subclasses. This method is solid and battle-tested as it is what powers many popular programming languages like Java and C++ as mentioned above but it has its downside.

One of the downsides of classical inheritance is that it is very verbose and you can quickly end up with a huge mass of collection and trees of objects that interact, that it can become very hard to figure out what is going on even if you use good practice. Plus you would have to learn and use a lot of intimidating keywords viz: friend, protected, private, interface, etc.

The class keyword, however, has been introduced to JavaScript in ES2015, but it is just syntactical sugar, JavaScript remains prototype-based

Prototypal inheritance is a much simpler approach. It is flexible, extensible, and very easy to understand. It is not a silver bullet anyway but it is in many ways better than class-based inheritance and it would be our focus going forward.

3. The Prototype Object And The Prototype Chain

To understand prototypal inheritance we need to understand these three key concepts viz: inheritance, prototype, prototype chain
Inheritance refers to a process whereby one object gets access to the properties and methods of another object.
Let's deal with these keywords with examples.

Remember in OOP we use objects to model real-world things that we want to represent inside our programs

const AppleInc = { name: "Apple", logo: "Apple fruit", operating_system: "Apple Software", on () { console.log("Turning on your " + this.name + " device") }, off () { console.log("Turning off your " + this.name + " device") } } console.log(AppleInc)

In the small contrived example above I have modeled the Apple company. It has a name, logo, and operating_system property, both an on(), and off methods(which are functions, meant to describe the behaviours of Apple devices).

We will proceed to model some products of Apple and have them inherit these properties.

All Apple products are unique and each reflects the image of the company.
You can certainly tell it is an Apple product even without seeing the logo; thus we can say in real-life, each Apple product inherits properties like design, operating system, etc from the company.

We will try to express this concept in codes as we demystify prototypal inheritance

const AppleInc = { name: "Apple", logo: "Apple fruit", operating_system: "Apple Software", on () { console.log("Turning on your " + this.name + " device") }, off () { console.log("Turning off your " + this.name + " device") } } const iPhone = { name: "iPhone", operating_system: "ios" } console.log(iPhone) iPhone.__proto__ = AppleInc // NEVER DO THIS IN REAL-LIFE. ONLY FOR DEMONSTRATION PURPOSE console.log(iPhone) // logs iPhone with AppleInc as its prototype. console.log(iPhone.on()) console.log(iPhone.off())

Kindly run the program in runkit by clicking the green run button to the right and click open the result of each console.log().
You would see an image like this:

alt console.log() result

Notice at the first console.log() iPhone does not have a proto object property. But after we assigned AppleInc as its prototype, in the second console.log() we can see a proto property, which is the AppleInc object.

Modern browsers allow us to set an object's prototype like this:

iPhone.__proto__ = AppleInc // sets AppleInc to be the prototype of the iPhone object.
Enter fullscreen mode Exit fullscreen mode

💥 Kindly note, I am setting this just for demonstration purposes. Do not set your object's prototype like this. Read more from MDN

I would also be writing about the recommended ways to set an object's prototype in JavaScript in the upcoming articles in this series

We can also notice that somehow we could call the on() and off() methods from our iPhone object. (But they are not originally there!).

We can see that the iPhone object has inherited the properties and methods of its prototype object. (An object from which another object inherits properties and methods. It lives as a property in that object with the name __proto__

As a result of this, we can call the on() and off() methods even when they are not originally in the iPhone object. This is possible because JavaScript holds a reference to the prototype of the iPhone object and when we try to access a property or method, it looks for it in the iPhone object first, and if it cannot find it there, it goes to its prototype (The __proto__ object property as seen) and looks for it there.
It returns the property or method once it finds it and stops the search.

This explains why:

iPhone.name // returns iPhone and not Apple
Enter fullscreen mode Exit fullscreen mode
const AppleInc = { name: "Apple", logo: "Apple fruit", operating_system: "Apple Software", on () { console.log("Turning on your " + this.name + " device") }, off () { console.log("Turning off your " + this.name + " device") } } const iPhone = { name: "iPhone", operating_system: "ios" } iPhone.__proto__ = AppleInc // NEVER DO THIS IN REAL-LIFE. ONLY FOR DEMONSTRATION PURPOSE console.log(iPhone.name)

The JavaScript engine finds the name property in the iPhone object returns it, and ends the search.

In JavaScript, a prototype can have its own prototype. So the AppleInc object can have its own prototype which can, in turn, have its own prototype and the process can go on. So when the JavaScript engine looks for a property or method in an object and cannot find it, it would go to its prototype and look for it there, if it does not find it, it would go to the prototype of that prototype and continue to go down the prototype chain until it finds it.

This chain of links or object references between an object, it's prototype, and the prototype of its prototype all the way down to the last prototype is called the prototype chain

const Company = { category: "Technology" } const AppleInc = { name: "Apple", logo: "Apple fruit", operating_system: "Apple Software", on () { console.log("Turning on your " + this.name + " device") }, off () { console.log("Turning off your " + this.name + " device") } } const iPhone = { name: "iPhone", operating_system: "ios" } AppleInc.__proto__ = Company // NEVER DO THIS IN REAL-LIFE. ONLY FOR DEMONSTRATION PURPOSE iPhone.__proto__ = AppleInc // NEVER DO THIS IN REAL-LIFE. ONLY FOR DEMONSTRATION PURPOSE console.log(iPhone.category) // gets this from the search down the prototype chain

Run the code above and see that we can access the category property even from the iPhone object because of the search down the prototype chain.

4. Exposing this Keyword in JavaScript

You might be surprised by this title, but I see an opening in this post to deal a little on the this keyword which has a reputation of being confusing in our beautiful language; JavaScript.
Let's visit our example again:

const AppleInc = { name: "Apple", logo: "Apple fruit", operating_system: "Apple Software", on () { console.log("Turning on your " + this.name + " device") }, off () { console.log("Turning off your " + this.name + " device") } } const iPhone = { name: "iPhone", operating_system: "ios" } iPhone.__proto__ = AppleInc // NEVER DO THIS IN REAL-LIFE. ONLY FOR DEMONSTRATION PURPOSE console.log(iPhone.on()) console.log(iPhone.off())

From our code above we understood that we are able to call the on() and off() methods from the iPhone object because of JavaScript's prototypal inheritance. (where we explained inheritance, prototype, and the prototype chain).
But why does this work correctly?

console.log(iPhone.on()) // returns Turning on your iPhone device
console.log(iPhone.off()) // returns Turning on your iPhone device
Enter fullscreen mode Exit fullscreen mode

How does it know the correct name is iPhone when the method is actually on the AppleInc object which is the prototype of the iPhone object and has its own name property?
It is because of the this keyword. It is pointing to the iPhone object; thus it gets the name property from it.

Take note, the this keyword in cases like this, starts its search from the object that originates the call, and since it found the name property in the iPhone object it points to it there.

Hold unto the above rule of thumb as we take a deeper look at this below:

The this keyword is a very interesting aspect of JavaScript it is extremely powerful and can sometimes be a little confusing. Below is the general rule of thumb to help you further understand the behaviour of the this keyword.

Kindly run the example code below and consider the result.

// New additions let name = "Brendan Eich" function sayName() { console.log(this.name) } let Person = { name: "Lawrence Eagles", sayName() { console.log(this.name) } } sayName() Person.sayName()

From the result, we can see that when you use the this keyword in a function it points to the global object but when you use it in a method (a function inside an object), it points to that object.
Following this, let's return to our code. I have added some extra properties to help us understand the this keyword better and consequently, understand our example more thoroughly.
Kindly run the code below and consider the results.

const Company = { category: "Technology", getNews () { console.log("viewing " + this.category + " news on my " + this.name + " device") } } const AppleInc = { name: "Apple", logo: "Apple fruit", operating_system: "Apple Software", store: "Apple Store", on () { console.log("Turning on my " + this.name + " device") }, off () { console.log("Turning off my " + this.name + " device") }, getDevice() { console.log("I just bought my " + this.name + " from " + this.store) } } const iPhone = { name: "iPhone", operating_system: "ios" } AppleInc.__proto__ = Company // NEVER DO THIS IN REAL-LIFE. ONLY FOR DEMONSTRATION PURPOSE iPhone.__proto__ = AppleInc // NEVER DO THIS IN REAL-LIFE. ONLY FOR DEMONSTRATION PURPOSE // let's buy an iPhone from the Apple store, then let's turn on and off our iPhone. console.log(iPhone.getDevice()) console.log(iPhone.on()) console.log(iPhone.off()) console.log(iPhone.getNews())

From the result of the code above we see that when we ran the method to buy an iPhone,

console.log(iPhone.getDevice()) 
// returns I just bought my iPhone from Apple Store
Enter fullscreen mode Exit fullscreen mode

the this keyword points to different objects to get the correct property. It starts by pointing to the object that originates the call and since it can find name property in the iPhone object it points to that. But it cannot find the store property in the iPhone object so it points to its prototype and looks for the property there and finds it.
This same principle holds when we tried to turn on/off our iPhone.

console.log(iPhone.on()) // returns Turning on my iPhone device
console.log(iPhone.off()) // returns Turning off my iPhone device
Enter fullscreen mode Exit fullscreen mode

Here the this keyword starts searching from the object that originates the call and because it can find the name property in it, it points to it there. Thus we get the correct device name even though the on() and off() methods are not in that object but in their prototype.
Finally, the result is the same when we try to read news from our iPhone device,

console.log(iPhone.getDevice()) 
Enter fullscreen mode Exit fullscreen mode

Notice the getDevice() method is in the Company object which is the prototype of the AppleInc object which is the prototype of the iPhone object. And because of the prototype chain, we can call getDevice() from the iPhone object as if it just sits in it.

Let's move forward.

5. JavaScript Object, The Prototype Of All Prototypes

When I said the JavaScript engine looks in an object for a property and if it cannot find it, it keeps going down the prototype chain until it finds it; you might have wondered what will be the last prototype?
Like in our case, what would be the prototype of the Company object?
Kindly run the code below and consider the result.

const Company = { category: "Technology" } const AppleInc = { name: "Apple", logo: "Apple fruit", operating_system: "Apple Software", on () { console.log("Turning on your " + this.name + " device") }, off () { console.log("Turning off your " + this.name + " device") } } const iPhone = { name: "iPhone", operating_system: "ios" } AppleInc.__proto__ = Company // NEVER DO THIS IN REAL-LIFE. ONLY FOR DEMONSTRATION PURPOSE iPhone.__proto__ = AppleInc // NEVER DO THIS IN REAL-LIFE. ONLY FOR DEMONSTRATION PURPOSE console.log(Company.__proto__) // gets this from the search down the prototype chain

You can see that the prototype of the Company object is the built-in JavaScript object. Since in Javascript everything is an object, all the other data types in JavaScript inherit properties and methods from the base object. So it is the final prototype in JavaScript.

Kindly open up the console.log() result of the code below from runkit and you would see something like this:
object prototype image
Notice some familiar names like the hasOwnProperty and the isPrototypeOf methods, the constructor etc

So the base object in JavaScript is the prototype of all data types in JavaScript, be it arrays, strings, numbers, functions, etc.

All data types in JavaScript have a prototype object that inherits its property and methods from the base object. Let's look at some of them in the next section.

6. The Power Of Prototypal Inheritance

Welcome to the power of prototypal inheritance. I am sure you would have already seen how flexible, extensible, and how easily objects can share properties and methods when using prototypal inheritance.

Let's look at some code examples for more:
Kindly run the codes below and open up each console.log() in runkit

const simpleArray = [] const simpleFunction = function simpleFunction () {} const simpleString = "" console.log(simpleArray.__proto__) console.log(simpleFunction.__proto__) console.log(simpleString.__proto__)

If you open up the first console in runkit you would notice that it has an Array Prototype which have a huge collection of properties and methods as seen in the image below.
alt Array prototype properties
Notice some familiar names here viz: concat(), every(), filter(), find(), pop(), map(), reduce() etc you can scroll up or down on runkit for more of them.
These are methods we use every day in JavaScript when we implement our own array. Notice they all sit in the Array Prototype Object.

Think of how much hassle it would be if we would have to write out all these properties and methods every time we create an array in our program!

If you open up the next console in runkit, you would get something like this:
alt Function prototype properties
Notice some familiar names like the call(), apply() and bind() methods, all present in the Function Prototype Object.
The prototype of all JavaScript functions is called the Function Prototype. It is actually an empty function. (a function with nothing in its code "{}" block

Finally, open up the last console in runkit and you would get something like this:
alt String prototype properties

Notice some familiar names like the length property, the split(), indexOf(), substring() methods and lot's more you can scroll down to see all

The prototype of all strings in JavaScript is called the String Prototype.

What do you think would be the prototype of all these prototypes viz:
array Prototype, Function Prototype, and the String Prototype?
Let's answer this with some more code examples.
Kindly run the codes below and consider the results in the console in runkit.

const simpleArray = [] const simpleFunction = function simpleFunction () {} const simpleString = "" console.log(simpleArray.__proto__) // array prototype console.log(simpleArray.__proto__.__proto__) // base object console.log(simpleFunction.__proto__) // function prototype console.log(simpleFunction.__proto__.__proto__) // base object console.log(simpleString.__proto__) // string prototype console.log(simpleString.__proto__.__proto__) // base object

From the above results, it is clear that the prototype of all prototypes in JavaScript is the base object.

We can also see a powerful pattern here. Prototypal inheritance allows us to write our properties and methods in one place and share them with other objects in our application. Hence both the array, function, and string prototype contains a huge list of properties and methods that is passed on to every array, function, and string declaration respectively in our program.

This saves us memory and time.

We can also use this power to create other Apple devices and have them get some properties and methods from the AppleInc object.

const Company = { category: "Technology" } const AppleInc = { name: "Apple", logo: "Apple fruit", operating_system: "Apple Software", on () { console.log("Turning on your " + this.name + " device") }, off () { console.log("Turning off your " + this.name + " device") } } const iPhone = { name: "iPhone", operating_system: "ios" } const iPad = { name: "iPad", operating_system: "ios" } const laptop = { name: "mac", operating_system: "mac os x" } AppleInc.__proto__ = Company // NEVER DO THIS IN REAL-LIFE. ONLY FOR DEMONSTRATION PURPOSE iPhone.__proto__ = AppleInc // NEVER DO THIS IN REAL-LIFE. ONLY FOR DEMONSTRATION PURPOSE iPad.__proto__ = AppleInc // NEVER DO THIS IN REAL-LIFE. ONLY FOR DEMONSTRATION PURPOSE laptop.__proto__ = AppleInc // NEVER DO THIS IN REAL-LIFE. ONLY FOR DEMONSTRATION PURPOSE // let's turn on and off all our apple devices console.log(iPhone.on()) console.log(iPad.on()) console.log(laptop.on()) console.log(iPhone.off()) console.log(iPad.off()) console.log(laptop.off())

7. Closing Thoughts

I do hope you followed through to this point. You are appreciated. It is a pretty long post but I want to believe that you got a thing or two. If you are not clear on any point or you have an addition, in case I miss anything, I would be looking forward to hearing from you in the comment section below.

Top comments (7)

Collapse
 
thekritsakon profile image
thekritsakon

Thank you to share your knowledge.
Nice explaination. 👏🏾

Collapse
 
karlenkhachaturyan profile image
Karlen Khachaturyan

Very generous of you to share this type of useful article. Thank you so much for explaining this.

Collapse
 
sagartyagi121 profile image
Gajender Tyagi

Great article! Helped me revise a few concepts and learn a few. Looking forward the thread series.Thanks.

Collapse
 
lawrence_eagles profile image
Lawrence Eagles

Thanks codecommando, I am glad to hear this.

Collapse
 
munjam profile image
RajendarM

A very good article to understand the core principle in a simple way. Thank you.

Collapse
 
churro90 profile image
churro90

Very clear and helpful article. Kind of new to this and struggling to find how can you use this in real applications using nodejs for example.

Collapse
 
rimounkaldas profile image
rimounkaldas

Thanks, that was great.