Classes in ECMAScript 2015

This post is 4 years old. (Or older!) Code samples may not work, screenshots may be missing and links could be broken. Although some of the content may be relevant please take it with a pinch of salt.

If you have worked with JavaScript before you may have heard the expression "prototypal inheritance" or that JavaScript is a "prototype" based language. ES2015 adds the class keyword to the language but it doesn't change its prototypal behaviour.

What prototypal inheritance really means is that JavaScript has special, hidden properties on objects called [[Prototype]], which is either set to null or references another object. (We can actually access this hidden property by calling .__proto__ on our objects.)

Introducing the prototype chain and its functions is outside the scope of this article but for our purposes we need to investigate what happens when we want to achieve 'object-oriented' like functionality in JavaScript. Again, the basics of object-oriented programming are outside the scope of this article.

Let's say we want to create a Car class. (This is a classic example that we could have taken from a Programming 101 class for any programming language). A car has properties, such as maximum speed, number of doors and the colour of the vehicle. Simply put, we can define the class, along with its properties and we can instantiate this class – meaning that we can create multiple cars based on our class. We can take this further and we can also create trucks, which inherit some of the properties of the parent class but it can extend it with its own properties.

We say that JavaScript is not a true object-oriented language because when a new instance is created (a new object instance that is) the functionality is not copied over to the new object like it would happen in other, true, object oriented languages but instead the functionality is linked via a reference chain that we refer to as the prototype chain.

Let's see this in action – we'll create our car object and assign some properties to it. First, we are going to take a look at ES5 to make sure that we understand how this particular functionality works in JavaScript and before digging deeper to classes in ES2015.

function Car(make, colour, speed) {
this.make = make;
this.colour = colour;
this.speed = speed;
this.getMaxSpeed = function () {
return 'Maximum speed is ' + this.speed + 'km/h.';
};
}

var car1 = new Car('BMW', 'black', 250);
var car2 = new Car('Audi', 'white', 240);

console.log(car1.getMaxSpeed()); // Maximum speed is 250 km/h.

In the first part of the code above we create a Car function specifying the properties – all the properties need to be passed in as an argument to the function. Later on, we create 2 variables, car1 and car2 – both of which are instances of the Car, achieved by calling the new function. Both car1 and car2 will hold all the properties defined in Car so we can not only access the .getMaxSpeed() function but we could also access the make and colour properties.

Now, let's take a look at how we can create a class to represent a truck. We have two options – the first one is to create another function repeating the arguments that we have for car, plus adding an extra property that's only relevant for trucks. This would be too much typing therefore we are going to apply inheritance to create our truck representation.

function Truck(make, colour, speed, wheels) {
Car.call(this, make, colour, speed);
this.wheels = wheels;
this.wheelCount = function () {
return 'This truck has ' + wheels + ' wheels.';
};
}

let truck = new Truck('black', 'MAN', '80', 6);
console.log(truck.getMaxSpeed(), truck.wheelCount());

Notice that we can still access the .getMaxSpeed() method and of course the method that we have created for the Truck as well.

class

So how would we go about doing this using the latest version of JavaScript – ES2015? Before we take a look at an implementation please remember this: the underlying prototype mechanism does not change.

As of ES2015 we can use the class keyword which really is just syntactic sugar on top of previously discussed prototype based system – using the class keyword merely helps us to write simpler code. Let's rework our previous examples to use classes:

class Car {
constructor(make, colour, speed) {
this.make = make;
this.colour = colour;
this.speed = speed;
}

getMaxSpeed() {
return `Maximum speed is ${this.speed} km/h.`;
}
}

const car1 = new Car('BMW', 'black', 250);
console.log(car1.getMaxSpeed());

The syntax in the code above is really simple. We are creating a class, adding a constructor to it which is a special function that is used when the objects gets created and initialised. (Please note that you can have only one constructor function per class).

Under the hood the above code gets translated to a very similar format that have specified earlier:

var Car = (function () {
function Car(make, colour, speed) {
this.make = make;
this.colour = colour;
this.speed = speed;
}
Car.prototype.getMaxSpeed = function () {
return 'Maximum speed is ' + this.speed + ' km/h.';
};
return Car;
})();
var car1 = new Car('BMW', 'black', 250);
console.log(car1.getMaxSpeed()); // Maximum speed is 250 km/h.

extends

Now that we have class like functionality we can also make use of the extends keyword and we can create a subclass of a main class – in other words we can achieve inheritance. And just like with classical inheritance we can overwrite the methods in the parent class and create new ones that we can call in the child class:

class Car {
constructor(make, colour, speed) {
this.make = make;
this.colour = colour;
this.speed = speed;
}

getMaxSpeed() {
return `Maximum speed is ${this.speed} km/h.`;
}
}

class Truck extends Car {
getMaxSpeed() {
return `Maximum truck speed is ${this.speed} km/h.`;
}

getMake() {
return `This truck is a ${this.make}.`;
}
}

const car1 = new Car('BMW', 'black', 250);
const truck = new Truck('MAN', 'black', 80);

console.log(car1.getMaxSpeed()); // Maximum speed is 250 km/h
console.log(truck.getMaxSpeed()); // Maximum truck speed is 80 km/h.
console.log(truck.getMake()); // This truck is a MAN.

super

There may be scenarios where, instead of overloading, we want to call a method from the parent class. We can achieve this by using the super keyword:

class Car {
constructor(make, colour, speed) {
this.make = make;
this.colour = colour;
this.speed = speed;
}

getMaxSpeed() {
return `Maximum speed is ${this.speed} km/h.`;
}
}

class Truck extends Car {
getMaxSpeed() {
console.log(super.getMaxSpeed()); // Maximum speed is 80 km/h.
return `This truck goes with ${this.speed} km/h.`;
}
}

const truck = new Truck('MAN', 'black', 80);
console.log(truck.getMaxSpeed()); // This truck goes with 80 km/h.

Another example could be used when we want to actually append the properties of the child class:

class Person {
constructor(name) {
this.name = name;
}

introduce() {
return `Hello ${this.name}`;
}
}

class SuperHero extends Person {
constructor(name, power) {
super(name);
this.power = power;
}

introduce() {
return `${super.introduce()}. Your superpower: ${this.power}`;
}
}

const dave = new SuperHero('Dave', 'invisibility');
console.log(
dave.introduce() // Hello Dave. Your superpower: invisibility
);

In the code example above we are extending a class called Person – a class that we could initialise by passing in the name of the person. Upon extending this class we want to append the values that get initialised when we create a new SuperHero – we want to add the name as well as a power as a class member. The only way we can do this is by adding super() in the constructor of the SuperHero class. Notice that we have also added a call to the parent's class .introduce() method in order to display the original greeting from the parent as well.