- What Is Object-oriented Programming (OOP)
- Classical vs Prototypal Inheritance
- The Prototype Object And The Prototype Chain
- The Power Of Prototypal Inheritance
- Closing Thoughts
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.
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.
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.
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
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
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:
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.
💥 Kindly note, I am setting this just for demonstration purposes. Do not set your object's prototype like this. Read more from MDN
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__
It returns the property or method once it finds it and stops the search.
This explains why:
iPhone.name // returns iPhone and not Apple
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
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.
Let's visit our example again:
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
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:
Kindly run the example code below and consider the result.
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.
From the result of the code above we see that when we ran the method to buy an iPhone,
// returns I just bought my iPhone from Apple Store
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
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,
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.
Like in our case, what would be the prototype of the Company object?
Kindly run the code below and consider the result.
Kindly open up the console.log() result of the code below from runkit and you would see something like this:
Notice some familiar names like the hasOwnProperty and the isPrototypeOf methods, the constructor etc
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
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.
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.
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:
Notice some familiar names like the call(), apply() and bind() methods, all present in the Function Prototype Object.
Notice some familiar names like the length property, the split(), indexOf(), substring() methods and lot's more you can scroll down to see all
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.
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.
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.