Blog icon

ES6 classes and JavaScript prototypes

By Sebastian Porto,
Sebastian Porto
Scroll down to read

ES6 introduces the class keyword. At first glance this looks like a completely new object model that follows classical languages like Java or Ruby, but behind the new syntax there is nothing new. The class syntax just provides an alternative way to create plain old JavaScript objects.

In this post I want to explain ES6 classes in the context of the JavaScript objects they produce.

Proto refresher

Let's start with a little refresher of prototype chains in JavaScript.

Every object in JavaScript has a special related object called the prototype. Prototypes are simple ways to share behaviour and data between multiple objects.

In code a prototype looks like this:

vehicle.__proto__ = machine
// machine is the prototype of vehicle

car.__proto__ = vehicle
// vehicle is the prototype of car

This is a prototype chain:

car -> vehicle -> machine

When JavaScript looks for a property that doesn't exist in a particular object (e.g. car), it will attempt to look for that property in each object on the prototype chain (e.g. first in vehicle, then in machine). It will walk along the chain until it finds the attribute or return undefined if it can't be found.

If you'd like to get a better understanding of this, please see this post.

ES6

In JavaScript we have Function Constructors, which have been the common way to build new objects until ES6. Unfortunately Function Constructors can be quite confusing to understand (especially if you want to model inheritance). To alleviate this, ES6 introduces the class syntax.

Classes in ES6 don't add any functionality to what we already have in the language, they are just a simpler syntax for building the same objects as we had before.

ES6 Classes

Take an ES6 class like this:

class Car {
    constructor(name) {
        this.kind = 'Car';
        this.name = name;
    }

    printName() {
        console.log('this.name');
    }
}

Let's create a car:

var mazda = new Car('Mazda');

This creates a JavaScript object. This object gets a prototype automatically assigned to it, more on this in a bit.

First let's explore the attributes assigned in the constructor.

mazda.hasOwnProperty('kind') // true
mazda.hasOwnProperty('name') // true

kind and name are properties directly assigned on the new mazda object. If you were to represent this object in an object literal it would look something like:

var mazda = {
    kind: 'Car',
    name: 'Mazda'
}

What about printName, where is this?

mazda.hasOwnProperty('printName') // false

It is not in the object itself, but we can call it:

mazda.printName() //Mazda

So we can deduce that it is somewhere in the prototype chain of mazda. Let's try the prototype of mazda:

mazda.__proto__.hasOwnProperty('printName') // true

This method was not copied when creating mazda, it is in the prototype.

Where did this prototype object came from?

This object is created automatically by JavaScript when declaring a class and assigned to all the instances of the class.

Let's create two cars now:

var mazda = new Car('Mazda');
var bmw   = new Car('BMW');

If we compare the prototypes of mazda and bmw we find that they are the same object:

mazda.__proto__ == bmw.__proto__ //-> true

Thus the method printName is being shared by all instances of Car. Adding methods on the prototypes of objects in JavaScript is an efficient way to conserve memory (as opposed to copying the methods on each object).

Prototypes are just objects that can be changed at runtime

Let's change the method printName after creating an instance:

var mazda = new Car('Mazda');
var bmw   = new Car('BMW');

mazda.__proto__.printName = function () { return 'Oops' }
bmw.printName() //-> Oops

Changing the method on the prototype of mazda changes the result on bmw as well. This is because they share the same prototype and we are changing that object.

What if we create yet another Car after doing this?

var honda = new Car('Honda');
honda.printName() // Oops

Oops, there is no going back, we have already modified the prototype for all instances of Car, now and in the future.

Remember, classes in JavaScript are not a blueprint like in other languages. They just define objects that can be modified at will in runtime.

To recap:

class Car {
    constructor(name) {
        // - this is the newly created object
        // - anything assigned here will be assigned
        //   to the object directly
        this.kind = 'Car';
        this.name = name;
    }

    // - class methods are assigned to the prototype of 
    //   the newly created object
    // - this prototype object will be shared by all instances
    printName() { 
        console.log('this.name');
    }
}

Inheritance

ES6 classes make inheritance much easier to model and understand than before:

class Vehicle {
    constructor(name) {
        this.kind = 'Vehicle';
        this.name = name;
    }

    printName() {
        console.log(this.name);
    }
}

class Car extends Vehicle {
    constructor(name) {
        super(name); //call the parent method with super
        this.kind = 'Car';
    }

}

Let's make some vehicles:

var boat  = new Vehicle('Boat');
var mazda = new Car('Mazda');

What is the relationship between these two?

boat.__proto__ == mazda.__proto__ // false

Ok, boat and mazda don't share a prototype. But we can call printName on both:

boat.printName() //-> Boat
mazda.printName() //-> Mazda

So, there's something shared here. What is happening is that the keyword extends is instructing JavaScript to create a prototype chain for us:

mazda.__proto__.__proto__ == boat.__proto__ //-> true

Let's double check by modifying this prototype:

mazda.__proto__.__proto__.printName = function () { console.log('Ha') }
boat.printName() // Ha

Conclusion

ES6 classes just create the same prototype chains we know and love from previous versions of JavaScript, but with a much saner syntax than Function Constructors.

I hope this post helps to clarify how classes work in ES6 and how they relate to objects in JavaScript.

To find out how reinteractive can turn your web application vision into reality, get in touch with us through our contact form or call us on +61 2 8019 7252.