Understanding ES6 classes the ES5 way

Danny Cornelisse
10-12-2017

ES6 has been out for a while now (especially considering it’s official name is EcmaScript2015) and it’s support by browsers is almost complete (except ie11, blegh):

https://kangax.github.io/compat-table/es6/

To me, one of the best features is the introduction of the Class keyword, which unsurprisingly lets you define classes:

class Human {
    
}

Now lets add a method:

class Human {
    sayHello () {
        console.log('hi');
    }
}

make a new instance:

var LeeroyJenkins = new Human();
console.log(LeeroyJenkins.sayHello()); // 'hi'

Easy right? Now lets see how to make this in ES5:

var Human = function () {};
Human.prototype.sayHello = function () {
    console.log('hi');
}

Our first observation is that a ES6 is not neccessarily a contructor. To add a constructor to an ES6 class:

class Human {
    constructor () {
        this.name = 'Leeroy Jenkins';
    }
    sayHello () {
        console.log('hi');
    }
}
var LeeroyJenkins = new Human();
console.log(LeeroyJenkins.name()); // Leeroy Jenkins'

When you log LeeroyJenkins you can see that the name property is added to the constructor as a new instance, while the sayName method is on the prototype, which is shared and not a new instance. In ES5:

var Human = function () {
    this.name = 'Leeroy Jenkins';
};
Human.prototype.sayHello = function () {
    console.log('hi');
}

This shows us that while in ES5 you had to add properties to the prototype specifically with Human.prototype.sayHello, while in ES6 you can add this to the class as a method. Constructor specific methods should be added to the constructor(){} part.

Like any constructor, you can set properties dynamically:

class Human {
    constructor (name) {
        this.name = name;
    }
    sayHello () {
        console.log('hi');
    }
}
var LeeroyJenkins = new Human('Leeroy Jenkins');
console.log(LeeroyJenkins.name); // Leeroy Jenkins'

in ES5:

var Human = function (name) {
    this.name = name;
};
Human.prototype.sayHello = function () {
    console.log('hi');
}
var LeeroyJenkins = new Human('Leeroy Jenkins');
console.log(LeeroyJenkins.name); // Leeroy Jenkins'

Now let’s extend the Human class:

class Developer extends Human {
   constructor (name) {
       super(name);
   }
}
var LeeroyJenkins = new Developer('Leeroy Jenkins');
console.log(LeeroyJenkins.name); // Leeroy Jenkins'

The first thing we notice is that we have to call the super method, with parameter name within the constructor for Developer. In this case, Human is the super for Developer. When instantiating a Developer with a name, that parameter must also be added to its constructor.

Now, the tricky part:

var Human = function (name) {
    this.name = name;
};
Human.prototype.sayHello = function () {
    console.log('hi');
}

var Developer = function (name) {
    this._super.call(this, name);
}

Developer.prototype = Object.create(Human.prototype);

Developer.prototype.constructor = Developer;

Developer.prototype._super = Human;

var LeeroyJenkins = new Developer('Leeroy Jenkins');
console.log(LeeroyJenkins.name); // Leeroy Jenkins'

GOOD LORD, this is DIFFICULT! Wow, hold on! Don’t freak out, bear with me:

Step 1: Create Human class (not really):

var Human = function (name) {
    this.name = name;
};
Human.prototype.sayHello = function () {
    console.log('hi');
}

Step 2: Create Developer class (not really):

var Developer = function (name) {
    this._super.call(this, name);
}

this._super.call(this, name); Lol what?. Allow me to explain:
this._super is merely setting a property on the constructor. ._super is just a convention to ‘fake’ a super. .call(this, name) makes sure that a new this context is set when we do:

Step 4: (We’re skipping a step)

Developer.prototype._super = Human;

Step 2 assigns the Human class (not really a class) to the prototype of Developer and step 4 makes sure that Human is on the prototype when Developer is instantiated i.e.
var LeeroyJenkins = new Developer('Leeroy Jenkins');

Before we can do this:
Step 3: Set prototype of Developer to be equal to that of Human

Developer.prototype = Object.create(Human.prototype);

Only then we can actually do step 4 above with the weird ._super method.

Step 5: Set constructor for Developer to equal its own prototype:

Developer.prototype.constructor = Developer;

Step: 6: instantiate Developer

var LeeroyJenkins = new Developer('Leeroy Jenkins');

The title of this blog is “Understanding ES6 classes the ES5 way”, but it makes no sense to do that. ES6 classes are far simpler than ES5 classes, but in my opinion it helps to understand ES6 classes if you know how constructors and prototypes behave when a class is instantiated.

To read more about classes in es5, read: https://www.accelebrate.com/blog/javascript-es6-classes-and-prototype-inheritance-part-1-of-2/

Cheers

LEAVE A REPLY

you might also like