Code Sharing: Classes vs. Prototypes

I originally wrote this article for students at Code First: Girls London. You may find it useful if you are interested in prototypal inheritance and JavaScript in general, and have some experience with classial inheritance. The article assumes some Ruby knowledge, but experience in any classical language (Java, C#, etc.) should translate well.

Introduction

On your coding adventures so far, you’ve probably already realized that in order to maintain and organize your programs, you need to introduce some sort of predictable and intuitive structure. A common problem is sharing data or functionality between separate parts of the program that need to do similar kinds of things.

One way of achieving this is with the concept of inheritance. You can think of it in the same way as biological inheritance – you inherit some attributes from your parents, but might still have attributes of your own that your parents don’t have. The most common form of sharing is called classical inheritance (classical as in “involving classes”). Lots of people feel that this works well in object-oriented languages (Rich Hickey would disagree!), and you’re probably already a little familiar with it from Ruby or another classical OO language.

But beware! Javascript has neither classes nor classical inheritance, but it’s often written as though it did! In fact, the language makes several provisions to help programmers “pretend” that they’re writing classes, which can be pretty confusing. For example, ECMAScript 6 (the 2015 version of JS) implements a class keyword. Wat?!

This has historically been because JS was often written by people who spend most of their time programming in other, likely classical, tech stacks (less so nowawdays because you can now write fullstack JS). So it was sensible for the language to provide familiar-feeling solutions to those developers. However, JS has its own way of sharing data and functionality: prototypal inheritance.

In this article we are going to learn about this very simple and powerful concept. Hopefully it will clear up any misconceptions you might have about JS and help you avoid future confusion.

Classes in Ruby

Let’s use classes in Ruby as a refresher on how classical inheritance can work. Say we’re bored on weekends and want a loving puppy to keep us company. We could write:

class Dog
    def initialize name
        @name = name
        puts "Why hello there. I am your new dog, and my name is #{@name}!"
    end

    def bark
        puts "WOOF!"
    end
end

This creates a “blueprint” for a type of thing that we’ve called Dog. When you ask for a new Dog, Ruby looks at the blueprint and gives you back an object that has all the methods and properties that we’ve specified in the class. This is called instantiation – making a specific thing (object) based on an abstract thing (class). Of course, in Ruby, classes are objects in their own right (instances of the class Class), but let’s not go there…

The initialize method runs once when the object is brought into life, so it’s often used to pass properties needed to make a class instance fully functional (for example, in this case the name argument gets saved in the instance variable @name and could be accessed later if needed).

With me so far? Cool, so let’s go get our puppy…

spot = Dog.new("Spot")
# Why hello there. I am your new dog, and my name is Spot!

spot.bark
# WOOF!

Nice. So now we have a barking dog. But what if we want to go for walkies? Guess we should add some form of walking functionality…

class Dog
    def initialize name
        @name = name
        puts "Why hello there. I am your new dog, and my name is #{@name}!"
    end

    def bark
        puts "WOOF!"
    end

    def walk
        puts "Aw yiss, going for walkies!"
    end
end

spot = Dog.new("Spot")
# Why hello there. I am your new dog, and my name is Spot!

spot.walk
# Aw yiss, going for walkies!

That’s nice and all, but doesn’t it feel weird somehow to have walk associated with Dog? Surely walking is not a dog-specific thing. A more accurate domain model would be to think of a dog as an example of one type of thing that is able to walk – for simplicity’s sake, let’s call it Animal. Sounds like another class to me…

class Animal
    def walk
        puts "Aw yiss, going for walkies!"
    end
end

So now we have dogs and animals. To give Dog access to walk, we need to somehow connect the two classes. That’s where inheritance comes in.

class Animal
    def walk
        puts "Aw yiss, going for walkies!"
    end
end

class Dog < Animal
    def initialize name
        @name = name
        puts "Why hello there. I am your new dog, and my name is #{@name}!"
    end

    def bark
        puts "WOOF!"
    end
end

spot = Dog.new("Spot")
# Why hello there. I am your new dog, and my name is Spot!

spot.bark
# WOOF!

spot.walk
# Aw yiss, going for walkies!

Dogs are a logical subset of animals, yes? All dogs are animals, but not all animals are dogs. So, by representing that in our code like above we can include all Animal code in the Dog class. You can use spot.methods to get an array of all the available methods on our puppy, and you’ll see that it includes both bark and walk!

spot.methods.include? :bark
# true

spot.methods.include? :walk
# true

The beauty of this design is that we can now create as many other kinds of animals as we like, and they’ll all be able to walk! And there you have it, the basics of classical inheritance.

“Classes” in JS >= ES6

As mentioned before, there is now a class keyword in JS. So you can now write things like this:

class Dog {
    bark() {
        console.log("WOOF!");
    }
}

const spot = new Dog();
spot.bark();
// WOOF!

Hooray. We now have a JS blueprint for a barking dog. For a more realistic example, let’s look at Facebook’s React, which can be written with this kind of setup:

import React from 'react';

class ExampleComponent extends React.Component {
    constructor(props) {
        super(props);
        this.setState({
            message: "Hello, world!"
        });
    }

    render() {
        return (
           // render some JSX
        );
    }
}

The idea here is that when new ExampleComponent() gets called, constructor runs first (like initialize in Ruby). The call to super means that the constructor on the superordinated class (the class that ExampleComponent inherits from – in this example React.Component) will also be called. The same concept exists in Ruby, Java, and other classical languages.

All seems pretty familiar, doesn’t it? Still, it’s important to remember that this is not the same as class in Ruby. In fact, it’s just a thin layer of syntactic sugar around the way people used to write fake classes in JS back in the day, before ES6 was a thing. That means that when you write a class in ES6, what you actually get is…

“Classes” in JS < ES6

You might already be aware that Javascript has first-class functions – you can pass functions around like variables and then use them whenever you like. You can also define constructor functions that can be used with the new keyword (but you have to use the function keyword rather than using the new ES6 arrow functions).

const Dog = function() {};
new Dog();
// Dog {}

FYI, constructor function names are capitalised by convention.

Interestingly, JavaScript functions are just key-value collections. What I mean by that is that, in essence, they’re just like hashes in Ruby, dictionaries in Python, or maps in many other languages. You can assign properties to them and access their properties later.

There are, however, a few things that distinguish functions from normal JS objects. For one thing, they are callable. That means that when you define a function, you can equip it with a block of code that it will remember and be able to execute when you ask it to.

Another of the special properties they have is called prototype. At the moment, we haven’t set a prototype on Dog:

Dog.prototype;
// undefined

Let’s play around with it now to see what it does:

const bark = function() {
    console.log('WOOF!');
};

Dog.prototype.bark = bark;

Dog.prototype;
// { bark: () }

const spot = new Dog();
spot.bark();
// WOOF!

We define a bark function, then add it to the Dog prototype. And like magic, our new dog spot is able to bark.

The class keyword in ES6 works exactly like this, it just saves you the trouble of dealing with prototype. But it writes the exact same code for you at the end of the day, so that the ES6 Dog from the previous section and the one we’ve just written are completely identical.

Prototypal inheritance: The prototype chain

OK, so we’ve seen how classes work in Ruby, and we’ve seen a very similar-looking thing in JavaScript. So why do I keep saying there are no classes in JS? Time for a peek under the hood to see how inheritance in JS actually works!

Every object in JS has a property called __proto__. You can think of this as a kind of “phone book”, or a lexion – whenever you ask an object for a property that hasn’t been defined on it, it can pass the request on to the object defined as its __proto__. This is known as delegation.

In this way, all JavaScript objects are linked together in a cooperative lookup arrangement known as the prototype chain. (If you’re into computer science, this is conceptually pretty similar to a linked list). If you follow this chain for long enough, you will always end up reaching Object, the final link.

new Object().__proto__;
// Object {}

new Array().__proto__;
// []

new Array().__proto__.__proto__;
// Object {}

new Dog().__proto__;
// function() {}

new Dog().__proto__.__proto__;
// Object {}

It is indeed objects all the way down (forgive the joke).

To summarise, the major difference between prototypal and classical inheritance is that the functionality of superordinated classes always becomes part of inheriting classes, while prototypes are simply independent objects that get delegated to by other objects.

There are many neat tools in JS, such as Object.create(), that let you exploit this prototypal functionality in order to knit together your objects in useful and elegant ways that allow you to “share” (read: delegate) functionality without locking your domain model into rigid class hierarchies.

Prototypal inheritance: Constructor functions

But Rufus, I hear you cry, earlier you were talking about prototype, and now we’re suddenly talking about this bizarre thing with four underscores called __proto__. What gives?

No worries – this final mystery, too, shall be divulged.

It turns out that a function in JS essentially gets two prototypes – __proto__ because it’s just another kind of object, and prototype just in case you want to use it as a constructor function. Every time you “instantiate” your constructor funciton, JS actually gives you back a brand new object with its __proto__ set your constructor’s prototype.

function CodeFirstGirls() {};

CodeFirstGirls.prototype.isFun = true;
CodeFirstGirls.prototype.javaScriptRating = "over 9000";

CodeFirstGirls.prototype;
// {
//    isFun: true,
//    javaScriptRating: "over 9000"
// }

new CodeFirstGirls().__proto__;
// {
//    isFun: true,
//    javaScriptRating: "over 9000"
// }

As a final aside, an interesting thing in ES6 is the introduction of arrow functions. This is a more elegant and concise way of writing functions: () => {} as opposed to function() {}. However, you can’t use ES6 arrow functions as constructors because they don’t have their own this context. But that’s a whole other conversation for another day, so relax if that sounds like meaningless gibberish to you. You can read more about arrow funtions here.

Try it yourself

If you’re feeling comfortable with what we’ve covered here so far, you can try to re-implement JavaScript’s new. The function should be called renew and implement prototypal inheritance.

Hint: you might want to look into Object.create.

Further reading

If this was interesting to you, I recommend that you pick up JavaScript: The Good Parts, by Douglas Crockford. It is the classic text on writing good JS, and what’s more, it’s short, readable, and funny (as well as highly opinionated). It includes an excellent introduction to prototypal inheritance.