Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

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. Exposingthis 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 aname, logo, and operating_system property, both anon(), andoff 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 PURPOSEconsole.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 eachconsole.log().
You would see an image like this:

alt console.log() result

Notice at the firstconsole.log() iPhone does not have aproto object property. But after we assigned AppleInc as its prototype, in the secondconsole.log() we can see aproto 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 modeExit 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 theon() andoff() methods from ouriPhone object. (But they are not originally there!).

We can see that theiPhone object hasinherited the properties and methods of itsprototype 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 theon() andoff() methods even when they are not originally in theiPhone object. This is possible because JavaScript holds a reference to the prototype of theiPhone object and when we try to access a property or method, it looks for it in theiPhone 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 modeExit 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 PURPOSEconsole.log(iPhone.name)

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

In JavaScript, a prototype can have its own prototype. So theAppleInc 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 theprototype 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 PURPOSEiPhone.__proto__ = AppleInc // NEVER DO THIS IN REAL-LIFE. ONLY FOR DEMONSTRATION PURPOSEconsole.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 theiPhone object because of thesearch down the prototype chain.

4. Exposingthis Keyword in JavaScript

You might be surprised by this title, but I see an opening in this post to deal a little on thethis 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 PURPOSEconsole.log(iPhone.on())console.log(iPhone.off())

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

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

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

Take note, thethiskeyword 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 aboverule of thumb as we take a deeper look atthis below:

Thethis keyword is a very interesting aspect of JavaScript it is extremely powerful and can sometimes be a little confusing. Below is the generalrule of thumb to help you further understand the behaviour of thethis keyword.

Kindly run the example code below and consider the result.

// New additionslet 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 thethis 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 thethis 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 PURPOSEiPhone.__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 modeExit fullscreen mode

thethis 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 findname property in theiPhone object it points to that. But it cannot find thestore 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 deviceconsole.log(iPhone.off())// returns Turning off my iPhone device
Enter fullscreen modeExit fullscreen mode

Here thethis keyword starts searching from the object that originates the call and because it can find thename property in it, it points to it there. Thus we get the correct device name even though theon() andoff() 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 modeExit fullscreen mode

Notice thegetDevice() method is in theCompany object which is the prototype of theAppleInc object which is the prototype of theiPhone object. And because of the prototype chain, we can callgetDevice() from theiPhone 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 theCompany 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 PURPOSEiPhone.__proto__ = AppleInc // NEVER DO THIS IN REAL-LIFE. ONLY FOR DEMONSTRATION PURPOSEconsole.log(Company.__proto__) // gets this from the search down the prototype chain

You can see that the prototype of theCompany object is thebuilt-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 thehasOwnProperty and theisPrototypeOf 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 firstconsole in runkit you would notice that it has anArray 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 theArray 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 nextconsole in runkit, you would get something like this:
alt Function prototype properties
Notice some familiar names like thecall(), apply() and bind() methods, all present in theFunction Prototype Object.
The prototype of all JavaScript functions is called theFunction Prototype. It is actually anempty function. (a function with nothing in its code "{}" block

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

Notice some familiar names like thelength 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 theString 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 theconsole in runkit.

const simpleArray = []const simpleFunction = function simpleFunction () {}const simpleString = ""console.log(simpleArray.__proto__) // array prototypeconsole.log(simpleArray.__proto__.__proto__) // base objectconsole.log(simpleFunction.__proto__) // function prototype console.log(simpleFunction.__proto__.__proto__) // base objectconsole.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 thearray, 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 PURPOSEiPhone.__proto__ = AppleInc // NEVER DO THIS IN REAL-LIFE. ONLY FOR DEMONSTRATION PURPOSEiPad.__proto__ = AppleInc // NEVER DO THIS IN REAL-LIFE. ONLY FOR DEMONSTRATION PURPOSElaptop.__proto__ = AppleInc // NEVER DO THIS IN REAL-LIFE. ONLY FOR DEMONSTRATION PURPOSE// let's turn on and off all our apple devicesconsole.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)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
thekritsakon profile image
thekritsakon
  • Joined

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

CollapseExpand
 
karlenkhachaturyan profile image
Karlen Khachaturyan
Exploring and enjoying the Developer life
  • Location
    Armenia Yerevan
  • Work
    Web Developer
  • Joined

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

CollapseExpand
 
sagartyagi121 profile image
Gajender Tyagi
Node.jsIBMiCI/CDand everything else.

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

CollapseExpand
 
lawrence_eagles profile image
Lawrence Eagles
Full-stack developer, instructor, and writer.
  • Email
  • Location
    Lagos
  • Work
    Senior software developer and Tech writer
  • Joined

Thanks codecommando, I am glad to hear this.

CollapseExpand
 
munjam profile image
RajendarM
  • Joined

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

CollapseExpand
 
churro90 profile image
churro90
  • Joined

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.

CollapseExpand
 
rimounkaldas profile image
rimounkaldas
  • Joined

Thanks, that was great.

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Full-stack developer, instructor, and writer.
  • Location
    Lagos
  • Work
    Senior software developer and Tech writer
  • Joined

More fromLawrence Eagles

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp