Prototypal Inheritance in JavaScript

Last year I wrote a post called “How to impress me in an interview”, and in it, I mentioned that I run across a lot of candidates (the vast majority, actually) who don’t really understand how prototypes work.

In a way, that’s kind of an amazing testament to how flexible JavaScript is, and how user-friendly many of today’s popular libraries are. I can’t think of many other object-oriented languages where an engineer can be reasonably productive without being at least vaguely aware of classes.

But here’s the thing: if you write JavaScript even semi-regularly, you really should understand how prototypal inheritance works. And that’s why I’m writing this post.

Disclaimer

There will be some things in this article that I over-simplify a little bit.

My goal here is to help someone who’s never used prototypal inheritance to become comfortable with it. In order to do that, I’ll cut a few small corners here and there if I think it helps eliminate a bit of confusion.

Objects Inherit from Objects

If you’ve ever read anything about inheritance in JS, then you’ve almost certainly heard that objects inherit from other objects.

This is true, and once you already understand it, it’s a good way to think about things. But my experience has been that this explanation alone isn’t really sufficient.

So I’m going to break from tradition here and not actually try to explain how inheritance works right up front. Instead, we’ll start with some simpler stuff that will hopefully provide a bit of context later on.

Function prototypes

Here’s a fun fact: In JavaScript, all functions are also objects, which means that they can have properties. And as it so happens, they all have a property called `prototype`, which is also an object.

function foo() {
}
typeof foo.prototype // ‘object’

That’s pretty simple, right? Any time you create a function, it will automatically have a property called prototype, which will be initialized to an empty object.

Constructors

In JavaScript, there’s really no difference between a “regular” function and a constructor function. They’re actually all the same. But as a convention, functions that are meant to be used as constructors are generally capitalized.

By the way, if you don’t know what I mean when I say “constructor”, that’s totally okay. We’ll get there.

So let’s say that we want to make a constructor function called Dog, because explaining inheritance using animals is a time-honored tradition and I’m kind of a nostalgic dude.

function Dog() {
}

If I want to make an instance of Dog, I use the new keyword. That’s really what I mean when I talk about constructors — I’m using the function to construct a new object. Any time you see the new keyword, it means that the following function is being used as a constructor.

var fido = new Dog();

So now we have fido, who’s a Dog. But he doesn’t really do anything.

He’s kind of like my dog, actually.

Methods

It’s time to make our Dog a little more dog-like. Bear with me here, because I’m going to explain this in just a minute.

function Dog() {
}
Dog.prototype.bark = function() {
console.log(‘woof!’);
};

You should remember from earlier that all functions automatically get initialized with a prototype object. In the example above, we tacked a function onto it called bark.

Now let’s make ourselves a new fido.

function Dog() {
}
Dog.prototype.bark = function() {
console.log(‘woof!’);
};
var fido = new Dog();
fido.bark(); // ‘woof!’

I’m going to explain this in more detail a little later on. But the important thing to take away right now is that by placing bark on Dog.prototype, we made it available to all instances of Dog.

Don’t worry yet about how this works. Just keep in mind that it works.

Differential Inheritance

JavaScript uses an inheritance model called “differential inheritance”. What that means is that methods aren’t copied from parent to child. Instead, children have an “invisible link” back to their parent object.

For example, fido doesn’t actually have its own method called bark() (in other words, fido.hasOwnProperty(‘bark’) === false).

What actually happens when I write fido.bark() is this:

1. The JS engine looks for a property called bark on our fido object.
2. It doesn’t find one, so it looks “up the prototype chain” to fido’s parent, which is Dog.prototype.
3. It finds Dog.prototype.bark, and calls it with this bound to fido.

That part is really important, so I’m going to repeat it:

There’s really no such property as fido.bark. It doesn’t exist. Instead, fido has access to the bark() method on Dog.prototype because it’s an instance of Dog. This is the “invisible link” I mentioned. More commonly, it’s referred to as the “prototype chain”.

Object.create()

Okay. We talked a little bit about differential inheritance and the prototype chain. Now it’s time to put that into action.

Since ES5, JavaScript has had a cool little function called Object.create().

Here’s how it works.

var parent = {
foo: function() {
console.log(‘bar’);
}
};
var child = Object.create( parent );
child.hasOwnProperty(‘foo’); // false
child.foo(); // ‘bar’

So what is it doing?

Essentially, it creates a new, empty object that has parent in its prototype chain. That means that even though child doesn’t have its own foo() method, it has access to the foo() method from parent.

Try It

We’ve covered a lot of ground so far, and we still have a bit more to go. To make sure everything we learned so far sinks in, I’d encourage you to open up your dev tools and try a quick little example.

Create a constructor function called Car. Add a drive() method to its prototype that just logs to the console. Now create an instance of Car and call drive().

I really mean it. You should take 30 seconds and go do it. I’ll wait here. And no cheating…

Good, you’re back. Hopefully you wrote something that looks like this.

function Car() {
}
Car.prototype.drive = function() {
console.log(‘vroom’);
};
var benz = new Car();
benz.drive(); // vroom

If not, consider re-reading some of the earlier sections. It’s important that you’re somewhat comfortable with that stuff before you move on.

Putting It All Together

It’s time to look at a (slightly) more real-world example that takes everything in this post and puts it all together.

Let’s start by creating a Rectangle constructor.

function Rectangle( width, height ) {
this.width = width;
this.height = height;
}

At this point, we’ll take a tiny detour to talk about this.

When a function is used as a constructor, this refers to the new object that you’re creating. So in our Rectangle constructor, we’re taking width and height as arguments, and assigning those values to the width and height properties of our new Rectangle instance.

var rect = new Rectangle( 3, 4 );
rect.width; // 3
rect.height; // 4

Now it’s time to give our Rectangle a method. Let’s call it area.

Rectangle.prototype.area = function() {
return this.width * this.height;
};

There’s that this keyword again. Just like in the constructor, this inside of a method refers to the instance.

var rect = new Rectangle( 3, 4 );
rect.area(); // 12

Subclassing

What if we want to make a new class of object that inherits from Rectangle?

Let’s say we need a class called Square. Hopefully you remember from elementary school that a square is just a specific type of rectangle.

We’ll start by creating its constructor.

function Square( length ) {
this.width = this.height = length;
}

So, that’s all well and good, but how do we make Square inherit from Rectangle? It’s all about setting up the prototype chain.

If you remember from earlier, we can use Object.create() to create an empty object that inherits from another object. In the case of Square, that means all we need to do is this:

Square.prototype = Object.create( Rectangle.prototype );

All instances of Square will automatically have Square.prototype in their prototype chain, and because Square.prototype has Rectangle.prototype in its prototype chain, every Square will have access to the methods of Rectangle.

In other words, we can do this:

var square = new Square( 4 );
square.area(); // 16

We can also add new methods that are specific to Square. Remember, Square.prototype is just an empty object right now (albeit with a link back to Rectangle.prototype).

Square.prototype.diagonal = function() {
return Math.sqrt( this.area() * 2 );
};

Time travel

One of the really cool (and potentially dangerous) things about inheritance in JavaScript is that you can modify or extend the capabilities of a class after you’ve defined it.

Because JavaScript will look up the prototype when trying to access properties on an object, you can alter your classes at runtime.

Here’s an example (for illustrative purposes only. Don’t ever do this):

var arr = [ 1, 2, 3, 4, 5 ];
Array.prototype.shuffle = function() {
return this.sort(function() {
return Math.round( Math.random() * 2 ) — 1;
});
};
arr.shuffle(); // [ 3, 1, 4, 5, 2 ]

The important thing to notice in this example is that arr was created before Array.prototype.shuffle existed. But because property lookups go up the prototype chain, our array got access to the new method anyway — because it existed on Array.prototype by the time we tried to actually use it. It’s like we went back in time and gave Array a (stupid) new method.

To get a sense of how powerful (and potentially dangerous) this is, go open the JS console on a page like Facebook, paste in the following code, and watch the log as you click around:

Array.prototype.push = function() {
throw new Error(‘lolnope’);
};

UPDATE:

Just wanted to leave one final note on constructors, which I realized may have been a little unclear…

function Rectangle( width, height ) {
this.width = width;
this.height = height;
};
Rectangle.prototype.area = function() {
return this.width * this.height;
};
var shape = new Rectangle( 3, 4 );

What the new keyword actually does here is, essentially:

  1. Create an empty object that has Rectangle.prototype in its prototype chain.
  2. Call Rectangle with the this bound to the new object.

So there’s really no magic there at all.

It’s kind of the same as saying:

function Rectangle( width, height ) {
this.width = width;
this.height = height;
};
Rectangle.prototype.area = function() {
return this.width * this.height;
};
var shape = Object.create( Rectangle.prototype );
Rectangle.call( shape, 3, 4 );

— -

If you live in the Boston area, love JavaScript, and want to work on some really exciting problems at Starry, shoot me an email. I’m hiring.