ES6 classes the easy way

Creating classes using Javascript is a strange affair with multiple exotic constructs, which adds up to a lot of confusion especially for novice coders. Now with the advent of ES6 finally we have a way to define and create class hierachies in an understandable way. It still falls a little short though. More of that later.

If you want to experiment with any of these patterns then you can use the repl.it Javascript sandbox straight from your browser(clicking the link opens a new tab). So without further ado let’s dig in.

The old way(s)

I will go through these patterns in turn, feel free to skip but I think it’s useful to become familiar as you will most likely come across some if not all of these at some point in your coding career.

Way 1. Object or ‘dot’ notation

var car= {
    name: 'Escort RS2000',
    make: 'Ford',
    sound: 'Beep!',
    show: function() {
        return this._showDetails();
    },
    _showDetails: function() {
        return `${this.make}-${this.name} goes ${this.sound}`;
  }
}

This example uses object notation using properties. Note that a property can define a variable or a function. Remember Javascript objects are nothing more than bags of properties at the end of the day!

console.log(car.name); // Escort RS2000
console.log(car.make); // Ford
console.log(car.show()); // Ford-Escort RS2000 goes Beep!

Way 2. Constructor invocation

This is possibly the most commonly used way of creating Javascript objects. It uses a special function prototype object to define the class. Over to Mr Crockford for an explanation…

JavaScript is a prototypal inheritance language. That means that objects can inherit properties directly from other objects. The language is class-free.

This is a radical departure from the current fashion. Most languages today are classical. Prototypal inheritance is powerfully expressive, but is not widely understood. JavaScript itself is not confident in its prototypal nature, so it offers an object-making syntax that is reminiscent of the classical languages. Few classical programmers found prototypal inheritance to be acceptable, and classically inspired syntax obscures the language’s true prototypal nature. It is the worst of both worlds.

If a function is invoked with the new prefix, then a new object will be created with a hidden link to the value of the function’s prototype member, and this will be bound to that new object.

Douglas Crockford – Javascript: The Good Parts

Here is our Car class created using this pattern. Firstly the Car function is declared. Then each class function is attached to the prototype. Calling new will attach the prototype to the Car function, allowing our class members be be accessed within the show and _showDetails functions.

function Car(name, make) {
    this.name = name;
    this.make = make;
    this.sound = 'Beep!';
}
Car.prototype.show = function() {
    return this._showDetails();
};
Car.prototype._showDetails = function() {
    return `${this.make}-${this.name} goes ${this.sound}`;
};
var car = new Car('Escort RS2000', 'Ford');
console.log(car.name); //Escort RS2000
console.log(car.make); //Ford
console.log(car.show()); //Ford-Escort RS2000 goes Beep!
console.log(car instanceof Car); //true

This way has the advantage of being able to do proper type checking which is always useful. This actually how ES6 classes work under the hood using a sprinkle of magic syntactic sugar!

Way 3. Object.create

var car=Object.create({
    name:{
        value: 'Escort RS2000',
        configurable: true,
        writable: false,
        enumerable: false
    },
    make: 'Ford',
    sound: 'Beep!',
    show: function() {
        return this._showDetails();
    },
    _showDetails: function() {
        return `${this.make}-${this.name} goes ${this.sound}`;
    }
});
console.log(car.name);  //Escort RS2000
console.log(car.make);  //Ford
console.log(car.show());//Ford-Escort RS2000 goes Beep!

So why would you use this construct? Firstly, you can assign extra options called property descriptors when defining the property. This is shown in the name property above. The bigger draw is that it allows you to build class hierarchies using prototype linking.

Consider the following example.

// Declare a `Car` object
function Car(name, make) {
    this.name = name;
    this.make = make;
    this.sound = 'Beep!';
}
Car.prototype.show = function() {
    return this._showDetails();
};
Car.prototype._showDetails = function() {
    return `${this.make}-${this.name} goes ${this.sound}`;
};

// Use the `Car` object to create an `Electric` car. First we define the prototype
// function, remembering to call the `Car` constructor
function ElectricCar(name,make) {
  Car.call(this,name,make); // call super constructor.
  this.sound = 'Laser blast!'; // override the sound prop
}

// Now we use `Object.create` to extend the `Car` object by binding to its prototype
ElectricCar.prototype = Object.create(Car.prototype); 

var elec = new ElectricCar('X', 'Tesla');
console.log(elec.name); //X
console.log(elec.make); //Tesla
console.log(elec.show()); //Tesla-X goes Laser blast!

Way 4. ES6 Classes

class Car {
  constructor(_name,_make) {
      this.name = _name;
      this.make = _make;
  }
  // Getter
  // get keyword allows you to define custom properties that can
  // be accessed using dot notation
  get show() {
    return this._showDetails();
  }
  _showDetails = function() {
    return `${this.make}-${this.name} goes ${this.sound}`;
  }
  get sound() {
    return 'Beep!';
  }
}

We have created a class using the class keyword. Each class has a constructor function.

Now we can create a new Car class using the new keyword. Note the special name property which returns the name of the class.

let car = new Car('Escort RS2000', 'Ford');
console.log(car.name); //Escort RS2000
console.log(car.show); //Ford-Escort RS2000 goes Beep!
console.log(Car.name); //Car
console.log(car instanceof Car); //true

Having created the class we can easily implement inheritance using the extends keyword.

class Truck extends Car {
  constructor(_name, _make) {
    super(_name, _make);
  }
  get sound() {
    return 'HONK!';
  }
}

Remember to call the base class constructor using the super keyword. In this example the sound property has been overridden to return ‘HONK!’

let truck = new Truck('SCANIA', 'R480');
console.log(truck.show); //SCANIA-R480 goes HONK!
console.log(truck instanceof Car); true
console.log(truck instanceof Truck); true

It’s interesting to mention again that under the hood, constructor invocation is used using prototype linking. This new way of defining a class is syntactic sugar used to simplify the creation of classes. One thing is missing however, This class has a private function denoted by the underscore. But there is nothing to stop you doing this:

truck._showDetails();//SCANIA-R480 goes HONK!

There are plans afoot to add the ability to define private functions using the # prefix as mentioned here but at present it has limited browser support.


References and further reading

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes