Understanding JavaScript Prototypes
Never heard of prototypes in JavaScript? Heard of them but still don’t quite understand them? Or maybe you have a pretty good grasp of them but there are a few things that throw you off. Hopefully this post will help clear up some of the mystery around what JavaScript Prototypes are and why you should care. If you aren’t familiar at all with prototypes, they come into play when you start working with objects and inheritance in JavaScript.
So, Why should I care?
In order to understand inheritance at all in JavaScript, you have to first understand prototypes. Prototypes are how inheritance is done in JavaScript and it comes into play in almost all of our JavaScript code whether we realize it or not. For example, even a simple array in JavaScript uses prototypes. If you don’t understand prototypes it’s easy to make some simple, but critical mistakes. This is especially true if you plan to use object-oriented concepts like inheritance in your own code. And, while it takes a bit of guided thinking to really understand prototypes, once you get them you’ll realize they aren’t that complex. So we might as well jump in and really grasp what they’re all about.
Constructor Functions
Constructor functions are used kind of like classes in object-oriented languages. And in fact, if you use classes in JavaScript, they essentially use constructor functions behind the scenes. Everything that we will talk about in this post is true for creating objects using classes too, but using constructor functions in this post will help us understand prototypes better.
So, consider the following constructor function:
function Cat(name, color) {
this.name = name;
this.color = color;
}
Cat.prototype.age = 0;
let fluffy = new Cat("Fluffy", "White");
In this code, Cat
is a constructor function. A constructor function is, really, no different than any other function.
In fact, the term “constructor function” is just a common nomenclature that suggests the function will be called with
the new
keyword and will be used to create properties and methods on the object being created (represented by this
).
When we say, let fluffy = new Cat("Fluffy", "White")
here’s what happens:
- The
new
operator creates a new, empty object. - The constructor function is called with the context of
this
set to the new, empty object. - The constructor function executes and sets the properties on the new, empty object.
Therefore, inside the function,
this.name = name;
is the same as sayingfluffy.name = name
. Hopefully, that helps to clarify that constructor functions are not doing anything magic, they are just normal functions that are creating properties onthis
— even though we use them in a way that is similar to how classes work in other languages. In fact, using thenew
keyword, you can create an object out of any function (it just doesn’t make sense with most functions).
Cat.prototype.age? What’s that all about?
Ok, first of all, notice that Cat.prototype.age
isn’t really part of the Cat
function.
It’s also helpful to realize when this code gets executed.
Seeing the code all lumped together like that might fool you into thinking that Cat.prototype.age = 0;
is being called when a new cat is created, which of course, on closer inspection is not true.
Usually this code (including the creation of the Cat
function above it) is called when a web page or JavaScript module is first loaded; thus making the Cat
function available to be called.
So think about what is happening there.
The Cat
function is being created and then the age
property is being created (with a value of 0) on the Cat
function’s prototype.
This all happens long before we even create any instance of a cat; it’s all just scaffolding.
Wait, functions have a prototype? What’s a prototype?
Functions can be used to create class-like functionality in JavaScript; and all functions have a prototype property. That prototype property is actually an instance of an object in memory and every function in JavaScript has one whether you use it or not. Every function (constructor function or not) has a prototype object instance hanging off of it, interesting, huh? That empty prototype object is created and stored in memory as soon as the function is declared.
So, let’s talk about how this prototype object is actually used after it’s created.
When you create a new object using the new
keyword, it creates the new object, passes it in as this
to the constructor function (letting that function do to it whatever it wants) and then, and this is the important part, it takes the object instance that is pointed to by that function’s prototype property and assigns it as the prototype for that newly instantiated object.
You might want to read that again, it’s important to understand that.
The new object is created, and the separate prototype object that is associated with the constructor function is assigned as the prototype for the new object.
So objects have a prototype property too?
Well, yes but you don’t access it the same way.
Objects do have a prototype but they do not have a prototype
property. Mostly because it’s a bad idea to mess with an object’s prototype directly.
So functions have prototypes and objects have prototypes.
And, they are very similar, in fact, a function’s prototype and an object’s prototype usually point to the same object in memory.
Here’s the main difference: An object’s prototype is the object in memory (not a function or class) from which it is inheriting properties.
This differs from a function’s prototype which is used as the object to be assigned as the prototype for new objects created using this constructor function.
An object’s prototype can be retrieved by calling myObject.__proto__
in most browsers but this property isn’t recommended as it’s not a standard property and could potentially be removed in future browser versions.
If you want to get access to it, it’s better to get it by calling Object.getPrototypeOf(myObject)
.
Having said this, I’ll use __proto__
going forward for simplicity.
So let’s give an actual definition to each of these terms:
- A function’s prototype: A function’s prototype is the object instance that will become the prototype for all objects created using this function as a constructor.
- An object’s prototype: An object’s prototype is the object instance from which the object is inherited.
Back to the code
All that explanation can be confusing without showing some examples, so let’s dive in. If you inspect the prototype of the cat in our example, you get the following result:
fluffy.__proto__;
// Cat {age: 0}
Side note: I used Chrome’s developer console to execute these statements and I will be displaying the results shown by chrome as comments (following //) as shown above.
If we take a deeper look, we can see that there is actually a prototype chain that was created.
Our new fluffy
object inherits from our Cat
prototype, but that prototype actually inherts from Object
.
fluffy.__proto__;
// Cat {age: 0}
fluffy.__proto__.__proto__;
// Object { }
This is almost always true; almost all objects inherit, eventually, from Object
.
And furthermore, all inheritance chains eventually end with null – meaning that you’ve reached the end of the inheritance chain:
fluffy.__proto__.__proto__.__proto__;
// null
Notice that fluffy
has a prototype (__proto__
) of Cat
.
Actually, to say it that way is not really accurate.
Cat is a function, so it cannot be a prototype; remember in the definitions above that a prototype is not a function it is an object instance.
This is important to remember when thinking about prototypes – an object’s prototype is not the function that created it but an instance of an object that is associated with that function.
Since this is important, let’s explore it a bit further:
Cat;
// function Cat(name, color) {
// this.name = name;
// this.color = color;
// }
Cat.prototype;
// Cat {age: 0}
fluffy;
// Cat {name: "Fluffy", color: "White", age: 0}
Look at the difference between Cat
, Cat.prototype
and fluffy
.
What this is showing is that Cat
is a function but Cat.prototype
and fluffy
are objects.
It further shows that Cat.prototype
has an age property (set to 0) and fluffy
has three properties – including age
…which isn’t actually true (stay tuned).
When you define a function, it creates more than just the function, it also creates a new object with that function as its type
and assigns that new object to the function’s prototype property.
When we first created the Cat
function, before we executed the line Cat.prototype.age = 0;
, if we would have inspected Cat’s prototype it would have looked like this: Cat {}
, an object with no properties, but of type Cat
.
It was only after we called Cat.prototype.age = 0;
that it looked like this: Cat {age: 0}
.
Inherited properties vs. native properties
In the above paragraph, I eluded to the fact that fluffy
didn’t actually have 3 properties as it seems.
That’s because age
really isn’t a direct property of fluffy
.
You can see this by executing these statements:
fluffy.hasOwnProperty("name");
// true
fluffy.hasOwnProperty("color");
// true
fluffy.hasOwnProperty("age");
// false
This is because age
actually belongs to fluffy
's prototype; and yet, if I execute the statement fluffy.age;
, it does indeed return 0
.
What is actually happening here, is when we ask for fluffy.age
, it checks to see if fluffy
has a property named age
, and if it does it returns it, if not, it asks fluffy
's prototype if it has an age
property.
It continues doing this all the way up the prototype chain until it finds the matching property or finds an object with a null prototype and if it doesn’t find the property in the prototype chain it will return undefined
.
But, if along the way, it finds an age
property, it stops and returns that value.
So that’s what prototype chaining is?
Yep. You may have heard of prototype chaining before. It is really quite simple to understand now that you (hopefully) understand a little more about how prototypes work. A prototype chain is basically a linked-list of objects pointing backwards to the object from which each one inherits.
Changing a function’s prototype
Remember that a function’s prototype is just an object, so what would happen if we started changing the properties of a function’s prototype after we created objects from it? Consider the following examples:
function Cat(name, color) {
this.name = name;
this.color = color;
}
Cat.prototype.age = 3;
var fluffy = new Cat("Fluffy", "White");
var scratchy = new Cat("Scratchy", "Black");
fluffy.age;
// 3
scratchy.age;
// 3
Cat.prototype.age = 4;
fluffy.age;
// 4
scratchy.age;
// 4
So, notice that changing the age of the Cat
function’s prototype property also changed the age of the cats that had inherited from it.
This is because when the Cat
function was created, so was its prototype object; and every object that inherited from it inherited this instance of the prototype object as their prototype.
Now consider the following example which actually changes the Cat functions prototype to point to a completely new object:
function Cat(name, color) {
this.name = name;
this.color = color;
}
Cat.prototype.age = 3;
var fluffy = new Cat("Fluffy", "White");
var scratchy = new Cat("Scratchy", "Black");
fluffy.age;
// 3
scratchy.age;
// 3
Cat.prototype = { age: 4 };
fluffy.age;
// 3
scratchy.age;
// 3
var muffin = new Cat("Muffin", "Brown");
muffin.age;
// 4
First of all, this is the important line of code in that example: Cat.prototype = { age: 4 };
.
Notice that I did not just change the value of the prototype.age
property to 4
, I actually changed the Cat
function’s prototype
to point to a new object.
So while muffin
inherited the new prototype object, fluffy
's and scratchy
's prototypes are still pointing to their original prototype object, which they originally inherited from the Cat
function.
This illustrates that a function’s prototype property is the object instance which will become the prototype (or proto) for objects created using this function as a constructor.
You might want to study this code for a minute and re-read this paragraph to really grasp what’s happening.
One more example to illustrate this point.
What would happen if I changed the value of the age
property of fluffy
's prototype? Would this be different than simply changing fluffy
's age? Yes, it would be different.
Think about the examples above, and about how age is not actually a property on fluffy, it is a property of fluffy’s prototype (proto).
So, given this setup:
function Cat(name, color) {
this.name = name;
this.color = color;
}
Cat.prototype.age = 3;
var fluffy = new Cat("Fluffy", "White");
var scratchy = new Cat("Scratchy", "Black");
Compare this example:
fluffy.age = 4;
fluffy.age;
// 4
scratchy.age;
// 3
To this example:
fluffy.__proto__.age = 4;
fluffy.age;
// 4
scratchy.age;
// 4
These produce different results because in the first example, we are just adding a new age
property to fluffy
.
So in the first example both fluffy
and fluffy.__proto__
have age
properties with the vales 4
and 3
, respectively.
When you ask for fluffy.age
, it finds the property on the fluffy
object so returns it immediately without ever looking up the prototype chain.
Whereas in the second example, fluffy
still does not have its own age
property, but its prototype (which is the same instance in memory as scratchy
's prototype) now has the new value 4
, thus affecting both fluffy
's and scratchy
's ages.
Multiple Inheritance
I have to confess, this is not something I use often because I tend to prefer composition over inheritance; but that is not to say it is without a place. Say you want to create a constructor function that “inherits” from another constructor function, much like you would do in other, object-oriented languages when you create one class that inherits from another. Let’s look at an example of how you may do this:
function Animal(name) {
this.name = name;
}
Animal.prototype.age=1;
function Cat(name, color) {
Animal.call(this, name);
this.color = color;
}
Cat.prototype = new Animal(null);
var fluffy = new Cat("Fluffy", "White");
fluffy.name;
// Fluffy
fluffy.color;
// White
fluffy.age;
// 1
fluffy.hasOwnProperty("name");
// true
fluffy.hasOwnProperty("color");
// true
fluffy.hasOwnProperty("age");
// false
Notice that age
is the only property that is not a direct property of fluffy
.
This is because when we called the Cat
constructor function, it passed in our new object (fluffy
/this
) to the Animal
function which created the name
property on the object.
The Cat
function then also added the color
property to fluffy
/this
.
But the age
property was added to the Animal
function’s prototype, it was never added directly to fluffy
.
Inheriting Functions
In all the examples above, I used properties like name
, color
, and age
to illustrate objects and inheritance.
However, everything that I have done above with properties can be done with functions.
If we were to create a speak()
function on the Cat
function like this: Cat.prototype.speak = function() { alert('meow'); };
, that function would be inherited by all objects that have Cat
as their prototype, just like with the name
, color
and age
properties.
Conclusion
If you are like me, it will take some experimenting for all this to really sink in. I’d recommend duplicating the above examples on your own and messing around with them to see what you can do — and find what surprises you. Prototypes, once you get them, are not that complex, but for me at least, it took playing around with some code for a bit before I really grasped them.