JavaScript Prototype Chain: Short And Simple Guide
Before I begin, let me give a warning. This article is for those who want to understand how the prototype chain in JavaScript works.
It's going to be tough for some of you and it may seem not very clear on first read. You may have to re-read this a few times to understand it.
But it's gonna be worth it. The question about the prototype chain is asked pretty often in technical interviews.
You probably already know that arrays and functions are just objects in JavaScript. This is crucial to understanding the prototype chain.JavaScript uses something called prototypal inheritance. You see these arrows here:
They're prototypal inheritance. And what does that mean?
Inheritance is basically an object getting access to the properties and methods of another object.
So, everything is an object in JavaScript. Array gets access to the properties and methods of the object. Same with functions. Through this chain we call the prototype chain, functions get access to the methods and properties of objects.
Now let's take a look at this:
First, we have an empty array called "food".
Next, we access this weird property called "__proto__". It is written with 2 underscores before and after the word proto. We can see in the console, this weird property is an array of functions:
constructor
,at
,concat
,copyWithin
,fill
,push
, etc.
Those are all methods that the array type of data gets by default.
So what we did here with this is getting up in the prototype chain. And getting into the Array
prototype (notice the image and capital A). Since the Array
is the "parent" of our "food" array we get all these methods inherited from.
Now, onto the next line, where we have this weird "__proto__" syntax 2 times. You might have guessed it right, with that, we go one more level up in the prototype chain. So we access the prototype of the "food" grandparent. And that's Object
.
This is the object that everything in JavaScript gets created from. Including functions and arrays.
For example, we have the toString
method over here. This means that anything that is a descendant of an Object
will get the toString
method. So that means that a "food" array has the toString
method on it.
You see I get the same result for a function type:
And this is what prototype inheritance is.
As a matter of fact, prototype inheritance is actually quite unique. It's not that common in other popular languages like C# or Java. They use something called classical inheritance while JavaScript uses prototype inheritance.
Even though in JavaScript we do have the class
keyword, there are actually no classes in JavaScript. We only have prototype inheritance.
So when you write a class
keyword, you are using a function in the background. This is what we call syntactic sugar.
You might ask yourself, where is the end of the prototype chain?
Well, it's easy to find out:
Here we have our "food" object and we go 2 levels up. So the first level is our parent Object
type, but if you go higher than that, you get null
.
In JavaScript, undefined
is often used to indicate a variable or object property has not been initialized or assigned a value. Or when a function doesn't return any value.
Null means there's absolutely nothing there. It represents the intentional absence of any object value.
You probably heard about linked lists, right? You can manifest the prototype chain in your head as a simple linked list:
All nodes are connected with their prototype chain links. The null
is just the last node in the linked list of the prototype chain.
Prototype chain in the code
Let's take a look at this code snippet:
const human = {
name: "Joe",
canTalk: true,
getIntelligence(){
return 10;
},
talk(){
if(this.canTalk){
console.log(`Hi, my name is ${this.name}!`);
}
}
}
const chimpanzee = {
name: "Mowgli",
getIntelligence(){
return 5;
}
}
Nothing special there, we have 2 simple objects:
human
Joe. He can talk and his intelligence is 10 points. When he talks he says: "Hi, my name is Joe!"chimpanzee
Mowgli. He can't talk and his intelligence is just 5 points.
Now, let's say I want to make Mogwli talk. How can I do that?
Well, I can use a trick with .bind
method and "borrow" talk function from a human
. Let's try this:
const talkingChimpanzee = human.talk.bind(chimpanzee);
console.log(talkingChimpanzee());
// prints undefined
Unfortunately, the chimpanzee doesn't have the canTalk
property set to true
. So even though we borrow the method because we don't have the talking ability.
So what can we do here?
We could add the property, but you can see how it might get more and more complicated here, right? What if we had a big object and we wanted to borrow more than one property/method?
And this is where prototype inheritance comes in.
We can create a prototype chain that will inherit all these properties and methods from a human.
chimpanzee.__proto__ = human;
chimpanzee.talk(); // prints "Hi, my name is Mowgli!"
console.log(chimpanzee.canTalk); // prints true
console.log(chimpanzee.getIntelligence()); // prints 5
We can see above is exactly what we needed.
The chimpanzee's intelligence is still 5 because it's defined as a chimpanzee object. So we are able to inherit through this prototype chain all the properties and methods of a human.
Then we override anything that we've already declared in our own object. In this case, it's property name
and method talk
.
hasOwnProperty method
Let me show you one more interesting thing:
console.log("Has name property: ", chimpanzee.hasOwnProperty('name'));
console.log("Has canTalk property: ", chimpanzee.hasOwnProperty('canTalk'));
// Has name property: true
// Has canTalk property: false
First, how is this even working?
We know that a chimpanzee doesn't have any method called hasOwnProperty
. A human also doesn't have that function.
Well, a human has also a "parent" from which it inherits all methods and properties. That parent is Object
and I mentioned that at the start. The Object
is a parent of all parents, the final parent.
So in the background, JavaScript is first trying to find the hasOwnProperty
function in a chimpanzee. But, it can't find it.
Then it goes up in the prototype chain and starts to search in a human. It can't find it.
Then, it goes one more level up in the prototype chain, in the Object
, and there we have that method:
Now we know why chimpanzee has that hasOwnProperty
method.
But what is the hasOwnProperty
method doing?
Well, it returns a boolean indicating whether this object has the specified property as its own property. So not inherited property, but its own.
That's why in the last code snippet, the hasOwnProperty
method displays true
for name
and false for canTalk
.
The chimpanzee has its own name
property defined in the object. So, canTalk
property is not its own. It's inherited from a human and that's why it displays false
.
Warning, warning, warning!
You might be asking yourself, how come I haven't seen this "__proto__" thing before?
It seems pretty useful. Why don't we see it in the projects?
What I've shown you, you shouldn't do in any project. And I mean this:
chimpanzee.__proto__ = human;
You shouldn't use it. Actually, you should NEVER use it!
It's bad for performance. There are different ways that we can use when it comes to prototype or inheritance.
So we never want to assign the prototype chain and create that chain ourselves. It's going to, mess up our JavaScript compiler pretty badly.
But I wanted to show you how it works.
What to use instead of "__proto__"?
Let's see how we can create our own prototypes and what is a safe way to do this.
Let's check this code:
const human = {
canTalk: true
}
const chimpanzee = Object.create(human);
chimpanzee.jumpOnTree = true;
console.log(chimpanzee);
// prints {jumpOnTree: true}
console.log(chimpanzee.canTalk);
// prints true
console.log(human.isPrototypeOf(chimpanzee));
// prints true
So we have here a human that can talk. And then we have chimpanzee that inherits all from human. We do that with the Object.create
method.
Now, there are many ways of doing this, and this is one of the ways that we can inherit from a human.
If we print a chimpanzee object all we can see is jumpOnTree
property. But if we explicitly access the that
property that is inherited from a human. We see it exists and is set to true
.
Last thing, we check if a human is a prototype of a chimpanzee with the isPrototypeOf
method. And we get true because we've created using Object.create
a prototype chain up to a human.
Now you know how to do this without using that evil "__proto__" thing.
As a matter of fact, they named it this way (with double underscores) so that nobody messes with the prototype chain.
Why is the Prototype chain useful?
The fact that objects can share prototypes means that you can have objects with properties that are pointing to the same place in memory. Thus being more efficient.
Imagine if we had a huge number of chimpanzees, right? And we copied all this functionality of the human onto the chimpanzee into different places in memory.
That could get overwhelming soon and blow up your code.
Instead of copying all this functionality into different places in memory, with prototype inheritance, we have it in one place.
Anything that inherits from a human will use this one instance of this method. Because the JavaScript engine is going to look up the prototype chain.
Comments ()