JavaScript prototypes

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 — 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.

Leave a Reply

Your email address will not be published. Required fields are marked *