With bite-size code snippets and easy explanations.
JavaScript prototypes are confusing and unfamiliar to many developers and engineers.
Today, it’s time to demystify and master prototypes once and for all. Doing so will give us the confidence to deal with prototypes when we inevitably encounter and use them in JavaScript.
This guide is divided into the following sections.
Each section builds on the previous one. As such, don’t skip sections, especially if you are reading this guide for the first time.
All code snippets are relevant and are cumulative across sections. Code snippets should work well with recent versions of JavaScript.
If you scroll through this guide too quickly, you may not gain much from it. Try to read this guide carefully and you will likely become convinced of what prototypes are and how to use them.
Creating a function has two effects.
The function itself will be created.Note that a function is also an object and thus it can have additional properties.
<Function>.prototype
.To illustrate these effects, create a function named Person
and observe that it automatically comes with a Person.prototype
object.
function Person(name) {this.name = name;}
typeof Person.prototype; // "object"
When we invoke the Person
function with the new
keyword, i.e. as a constructor, a new object this
is implicitly created, this.name
is set, and finally, this
is implicitly returned.
function Person(name) {this.name = name;}
const alex = new Person("Alex");typeof alex; // "object"alex.name; // "Alex"
Importantly, the object alex
and any other object constructed from Person
will gain indirect access to Person.prototype
.
Let’s add a greet
function to Person.prototype
. Notice that the existing object alex
can now greet
, and a newly created object tom
can do the same. This form of code reuse is known as prototypal inheritance.
Person.prototype.greet = function() {console.log(`Hi ${this.name}`);}
alex.hasOwnProperty("greet"); // falsealex.greet(); // "Hi Alex"
const tom = new Person("Tom");tom.greet(); // "Hi Tom"
Even though the object alex
constructed from Person
does not have a greet
property on itself, it was able to access Person.prototype
and thus invoke Person.prototype.greet
with this
being implicitly set to alex
, which results in “Hi Alex” being logged to the console.
The other object tom
gains access to the same Person.prototype
object in a similar way.
The traversal algorithm consults the object’s prototype when it cannot find the desired property on the object. If it finds the property on the prototype, the traversal stops. Otherwise, it will consult the prototype of the prototype, and so on, until it finds the property or it reaches the end of the prototype chain.
At each traversal, the object delegates the responsibility of doing something to its prototype if it does not know how to do it. alex
did not know how to greet
, so alex
asked Person.prototype
for help on how to greet
.
Let’s use Object.getPrototypeOf
to check alex
’s entire prototype chain.
Object.getPrototypeOf(alex) === Person.prototype; // true
The line above tells us that the prototype of alex
is Person.prototype
. Therefore, if JavaScript cannot find the desired property on alex
, it will check Person.prototpe
.
In other words, alex
’s prototype chain starts with Person.prototype
.
If JavaScript is still unable to find the desired property on Person.prototype
, it will look at the prototype of Person.prototype
, which is Object.prototype
.
Object.getPrototypeOf(Person.prototype) === Object.prototype; // true
Why is Object.prototype
the prototype of Person.prototype
?
Suppose that Person.prototype
, which is an object, was constructed from the built-in Object
constructor (whether or not this is the case is an implementation detail).
You can observe a pattern consistent with what we have learned so far.
alex
was constructed from Person
.The prototype of alex
is Person.prototype
.
Person.prototype
was constructed from Object
.The prototype of Person.prototype
is Object.prototype
.
You can go from statement 1 to 2 by first substituting Person
with Object
, and then substituting alex
with Person.prototype
.
To recap, we have seen that the prototype of alex
is Person.prototype
, and the prototype of Person.prototype
is Object.prototype
. Therefore, alex
’s prototype chain contains Person.prototype
followed by Object.prototype
.
Is Object.prototype
the final prototype in alex
’s prototype chain? Yes, because Object.prototype
does not have a prototype (it is null).
Object.getPrototypeOf(Object.prototype) === null; // true
Even though Object.prototype
is an object, its prototype is not Object.prototype
, otherwise we will have an infinite prototype chain.
Nearly all other objects in JavaScript have Object.prototype
at the end of their prototype chains. We have seen how it is so for the object alex
which was constructed from Person
. This property also applies to plain objects created from the built-in Object
constructor and the object literal syntax.
const constructedObject = new Object();const objectLiteral = {};
Object.getPrototypeOf(constructedObject) === Object.prototype; // trueObject.getPrototypeOf(objectLiteral) === Object.prototype; // true
The fact that nearly all objects have Object.prototype
at the end of their prototype chains is of practical significance because they will have access to common utilities offered by Object.prototype
such as toString
and valueOf
.
alex.toString(); // "[object Object]"alex.valueOf(); // Person { name: "Alex" }
Earlier, we saw that alex
has a name
property which can be accessed using alex.name
. The syntax does not say anything more about alex
, but we usually add more meaning to it. We think that alex.name
is not just any random name that happens to be accessible at alex.name
, but instead refers to Alex’s name.
What about Person.prototype
? Does it refer to Person
’s prototype?
Nope!
Object.getPrototypeOf(Person) !== Person.prototype; // true
If Person.prototype
does not refer to Person
’s prototype, then whose prototype does it refer to?
Well, Person.prototype
will become the prototype of objects constructed from Person
. We have already seen this behavior with alex
.
It may help to think of Person.prototype
as a gift that Santa deposited at your house, but that gift is meant for your kids and is not yours.
So what is the actual prototype of Person
? It is Function.prototype
.
Object.getPrototypeOf(Person) === Function.prototype; // true
This is because Person
is a Function
and thus it has a prototype of Function.prototype
.
Function.prototype
offers common utilities like call
, bind
and apply
which can be accessed from Person
and other functions.
We can also create prototype chains without constructors.
const greeter = {greet() {console.log(`Hi ${this.name}`);}};
const bob = Object.create(greeter);bob.name = "Bob";bob.greet(); // "Hi Bob"
Object.getPrototypeOf(bob) === greeter; // true
In the above example, Object.create
created a new object with greeter
as its prototype. That object was then assigned to bob
. Although bob
does not have its own greet
function, it is able to access the greet
function on its prototype greeter
.
Creating a new object with a defined prototype using Object.create
is more straightforward than having to deal with a constructor function and the <Constructor>.prototype
object.
The use of Object.create
can be combined with factory functions. Unlike constructor functions, factory functions explicitly return an object and are not invoked with new
. Here is an example.
function createPerson(name, prototype) {const person = Object.create(prototype);person.name = name;return person;}
const ada = createPerson("Ada", greeter);ada.greet(); // "Hi Ada"
Given their simplicity and conciseness, the use of factory functions and Object.create
tends to be the preferred approach over the use of constructor functions and the <Constructor>.prototype
objects.
“Because someone came up with it” is not a satisfactory answer.
The word prototype is often glossed over in JavaScript literature. Most people treat it as a technical term and do not explain why it is suitably named “prototype”. Knowing why a prototype is called a prototype can give us a better mental model to work with whenever we encounter it.
That being said, it is hard to find an exact answer to this question. Here is my take on this question.
A prototype in real life mainly refers to
After substituting the word “product” with “object”, we see that a JavaScript prototype refers to
Point 1 highlights a distinct point about prototypal inheritance as compared to traditional class-based inheritance. The prototype is an object that can be used on its own. However, a traditional class is not an object and thus cannot be used like an object.
Note that although later versions of JavaScript have a class
keyword, it still uses prototypal inheritance under the hood.
Point 2 is valid because a prototype typically has fewer properties compared to an object that uses it as its prototype. For example, both alex
and Person.prototype
are able to greet
(although alex
does it with the help of Person.prototype
), but alex
has an additional name
.
As for point 3, a JavaScript object does share some characteristics of its prototype(s) because of prototypal inheritance.
Since objects are linked in the prototype chain, if you change a prototype, the behavior of existing and future objects linked to that prototype could be affected.
As such, be careful not to change a built-in prototype unless you are trying to polyfill a standard feature. If everyone took the liberty of changing built-in prototypes arbitrarily, there will be conflicts and broken code.
You have seen what prototypes are, how JavaScript traverses the prototype chain to access prototype properties, and thus how code is reused in what we call prototypal inheritance. You have also seen how to use prototypes, with or without constructor functions.
Though the mastery of prototypes has eluded many, I hope that this definitive guide has helped you to master prototypes in JavaScript and thus become a better software developer and engineer.