Understanding the Prototype Chain
This article is the first in a comprehensive five part series on Prototypal Inheritance.
Part 1 — Understanding the Prototype Chain
Part 2 — Prototypal Inheritance with Object.create
Part 3 — Prototypal Inheritance with Constructor Functions
Part 4 — Coming soon!
Part 5 — Coming soon!
When I transitioned from Java and C++ to JavaScript, I found the concept of Prototypal Inheritance extremely confusing. I simply could not get my head around it. It was too different from what I was used to.
Thus, I created this series to help developers who, much like my past self, are new to JavaScript and are trying to gain an in-depth understanding of prototype mechanics.
To make sure that you completely grasp all the concepts presented in this series, I provide exercises (and solutions) at the end of each part. I highly, highly recommend going through each exercise.
Feel free to reach out with questions! I would love your feedback and any constructive criticism you may have.
Part 1 — Understanding the Prototype Chain
In JavaScript, every object has a special connection called [[prototype]] that links it to another object known as its prototype. This prototype object can have its own prototype, forming a chain. The chain keeps going until it reaches an object with null as its prototype.
To visualize this, imagine a family tree, where each person has a parent, who in turn has their own parent, and so on. Similarly, objects in JavaScript have a prototype, and that prototype can have its own prototype as well.
Here is an example of a prototype chain:
Most JavaScript objects have Object.prototype
as their prototype. However, there are certain objects that do not inherit directly from Object.prototype
. For example:
- Objects created via
Object.create(someProto)
havesomeProto
as their prototype. I’ll do an in-depth dive ofObject.create
in Part 2 of this series. - Certain built-in objects in JavaScript, like
Array
,Function
,RegExp
, andDate
, possess their own distinct prototypes (Array.prototype
,Function.prototype
,RegExp.prototype
, andDate.prototype
, respectively). Although the prototypes of these built-in objects might ultimately link toObject.prototype
, the objects themselves do not directly haveObject.prototype
as their immediate prototype.
When attempting to access a property of an object, the search for that property extends beyond just the object itself. It continues on to the object’s prototype, the prototype of the prototype, and so forth, until either a property with a matching name is discovered or the end of the prototype chain is reached. If the search fails to find the desired property, the outcome of the operation is undefined
for a value property or TypeError
for a function-valued property.
I’ll illustrate the concept above by walking through two examples:
Example 1
const pumba = {
name: 'Pumba',
age: 15
}
console.log(pumba.toString()) // [object object]
When we declare an object literal, like the one named pumba
in the snippet above, it automatically getsObject.prototype
as its prototype. Essentially, our little snippet above created the following prototype chain:
Notice that we never defined toString
on pumba
, so where did it come from? When we tried to access toString
, and it wasn’t found on pumba
, the JavaScript runtime checked pumba
’s prototype, which happens to be Object.prototype
. That object does, indeed, have the toString
method.
In fact, if we console.log(Object.prototype)
in the chrome console, we see the toString
method along with other methods you might be familiar with.
Example 2
const arr = ['Pokity', 'Wokity', 'Lokity'];
console.log(arr.at(1)); // Wokity
How did our array, arr,
get the at
method? As mentioned above, the [[prototype]] reference of an array points to Array.prototype
. I.e, the code above produces the following prototype chain:
If we examine the Array.prototype
object in chrome’s console, we see
Array.prototype
has the at
method.
Now that we understand the prototype chain, the natural next question is, how do we access the prototype of an object obj
? There are two primary ways:
Object.getPrototypeOf(obj)
obj.__proto__
const newObj = {
key: 'value'
}
console.log(newObj.__proto__ === Object.prototype); // true
console.log(Object.getPrototypeOf(newObj) === Object.prototype); // true
We will stick to Object.getPrototypeOf
since the use of __proto__
is no longer recommended. For more details as to why, see here.
By this point, you should have a good overview of how the prototype chain works. Let’s move on and discuss the three primary ways of creating a prototype chain:
- Object.create
- Constructor Functions
- Class-Syntax Constructors
Continue to Part 2, Prototypal Inheritance with Object.create.
Exercises
Exercise 1
const myBirthday = new Date('December 11 1986');
console.log(myBirthday.getFullYear()); // 1986
We never defined the function, getFullYear
, on myBirthday
.
In fact, the statement myBirthday.hasOwnProperty(‘getFullYear’)
evaluates to false
.
Why are we not getting a TypeError
when running the above code?
Solution
Date
objects have Date.prototype
as their prototype. Thus, we have the following prototype chain:
null ← Object.prototype ← Date.prototype ← myBirthday
You can verify this by executing the following code
console.log(Object.getPrototypeOf(myBirthday) === Date.prototype);
console.log(Object.getPrototypeOf(Date.prototype) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype) === null);
Date.prototype does have the getFullYear method. The statement Date.prototype.hasOwnProperty(‘getFullYear’)
evaluates to true
.
Thus, when the JavaScript runtime could not find the getFullYear
method on the myBirthday
object, it checked it’s prototype, Date.prototype
. That object does indeed have the getFullYear
method, and so it executed it.
Full Disclosure: I am a developer at heart. I prefer writing things like dog.isWalking as opposed to ‘the dog is walking’. Given that I’ve been doing this for years, my English grammar skills have slowly but surely been deteriorating. As I was working on this series, I noticed that, well, at times, what I wrote sounded like gibberish. And so, I leaned on ChatGPT. I also tried generating images for this article using DALL·E 2; unfortunately, they were all … weird.