This is the first in a series of posts intended as a gentle guide through the realm of JavaScript prototypes. If you’re coming here from a class-based language like Java, PHP, C#, or C++ I suggest you put aside everything you know about how objects and inheritance work in those languages. Try to treat the way JavaScript works as its OwnThing.
Context and Motivations
You probably know how to create an object literal — let’s say for a person:
var fuzz = { name: 'Fuzz', greeting: 'Hello. My name is', sayName: function () { console.log(this.greeting, this.name); } };
This works fine for one-off objects. If you need to create more than one person, you could create a factory function:
function makePerson(name) { return { name: name, greeting: 'Hello. My name is', sayName: function () { console.log(this.greeting, this.name); } }; } var fuzz = makePerson('Fuzz'); var athena = makePerson('Athena');
or a constructor function:
function Person(name) { this.name = name; this.greeting = 'Hello. My name is', this.sayName = function() { console.log(this.greeting, this.name); } } var fuzz = new Person('Fuzz'); var athena = new Person('Athena');
In both of the above, fuzz
and athena
each have their very own copies of name
, greeting
, and sayName
. For a small set of small objects, this is fine. But consider that every Person so defined has properties whose values are common to all Persons (sayName
and, arguably, greeting
). Wouldn’t it be nice if there was a convenient place where we could put shared stuff like this and only refer to that one copy instead of making each object carry around its own copy? This could save a lot of memory if there are a lot of Persons or if a shared property happens to be really big.
Also, what if we want to create a kind of Person that, say, is a bit more specialized — like a Teacher. A Teacher might be identical to a Person except that they have an additional subject
property and a saySubject
method. Wouldn’t it be nice if we could just specify how a Teacher is special in relation to a Person rather than copy/paste the code we already wrote for Person?
Enter the Prototype
JavaScript offers a solution to both of these via the prototype. Every function in JavaScript automatically gets a special property attached to it called prototype
:
function bar () { }; console.log(bar.prototype);
A prototype
is an object with a bunch of properties, including, curiously, one called constructor
that points back to the function itself.
if (bar.prototype.constructor === bar) { console.log("That's cute."); } else { console.log('You lied.'); };
Going further, when you instantiate an object it will automatically recieve a property called __proto__
that is a reference to its constructor’s prototype
.
function Person(name) { this.name = name; this.greeting = 'Hello. My name is', this.sayName = function() { console.log(this.greeting, this.name); } }; var fuzz = new Person('Fuzz'); if (fuzz.__proto__ === Person.prototype) { console.log("That's cute."); } else { console.log('You lied.'); };
If you try to access an object’s property, the JavaScript engine will first look in the object itself for it; if it doesn’t find it there, it will look in __proto__
(i.e., the constructor’s prototype
) for it.
Let’s set a property on Person.prototype
and see what happens:
Person.prototype.deepDarkSecret = "I descended from apes." if (fuzz.hasOwnProperty('deepDarkSecret')) { console.log('Haz secrets.') } else { console.log('No secrets.') }
No secrets.
console.log( fuzz.deepDarkSecret );
Oops!
The deepDarkSecret
is actually a property in Person.prototype
, a.k.a., fuzz.__proto__
.
if (fuzz.deepDarkSecret === fuzz.__proto__.deepDarkSecret) { console.log("That's cute."); } else { console.log('You lied.'); };
And when we added it to the prototype, it became available to all instances of Person.
athena = new Person("Athena"); console.log(athena.deepDarkSecret);
Not just a cute party trick
The upshot of this is that a constructor’s prototype
is a convenient place to stuff the properties of objects made with it whose values are common to all objects. Most methods fit into this category as well as potentially other properties:
function Person(name) { this.name = name; } Person.prototype.greeting = 'Hello. My name is'; Person.prototype.sayName = function() { console.log(this.greeting, this.name); }; var fuzz = new Person('Fuzz'); var athena = new Person('Athena'); fuzz.sayName(); athena.sayName();
fuzz
and athena
now each have their own name
properties. However, they are using the common greeting
and sayName
properties, saving the memory it would have required for them to have their own copies of those.
When you ask athena
to say her name, the JavaScript engine first looks for the sayName
identifier in the athena
object. But it won’t find it there. So the next place it looks is athena.__proto__
, which is the constructor’s prototype
, and it finds it there.
if (fuzz.name !== athena.name) { console.log("That's what I expeceted.") } else { console.log("You lied.") } if (fuzz.greeting === athena.greeting) { console.log("That's what I expeceted.") } else { console.log("You lied.") } if (fuzz.sayName === athena.sayName) { console.log("That's what I expeceted.") } else { console.log("You lied.") }
In the next post, we’ll have a look at what happens when you manipulate properties you’ve set on prototypes.
*The use of the __proto__
identifier for this appears to be true of browsers. In Node.js it might be different.