The single most important development on the JavaScript scene recently has been the standardization of classical syntax in ECMAScript 6 (ES6 or ES2015). Of course, this is opinion but the new class syntax really does help if you have been writing JavaScript in a pseudo classical form.
Why Use Classes?
JavaScript already supports objects as first class citizens and you can even
skip the new
operator entirely. However this route can lead to chaos quite easily.
If you ask me, classes are about code organisation more than anything else. It’s easier to think in terms of: ‘the function accepts a MyClass instance’ than ‘ the function accepts an object returned from calling X function’.
Arguably however, the organisation I’m referring to comes from having types and recognizable interfaces we can refer to.
I think ES6 classes bring us closer to thinking in terms of interfaces rather than strictly passing behaviours around.
Before ES6
In ES5, to simulate classes, we use a combination of constructor functions and prototype objects.
Whenever new
is called on a function, the JavaScript engine creates a new object
within the function’s body scope and sets the value of this
to that object.
The object is given a __proto__
property that is copied from the function’s prototype
property and
acts as the object’s prototype.
A prototype is a special property on any JavaScript object that is consulted whenever a property request is made that the object can’t fulfil from its own properties (functions on objects are properties as well).
Prototypes are objects and also have their own prototypes.
Think of it as a chain or a reverse tree that we traverse when someone asks us for a value we do not already own, hence prototype chain.
function MyClass() {}
MyClass.prototype.doSomethingSpecial = function () {
console.log('Not thanks!');
}
If we had added doSomethingSpecial
to the MyClass
function directly instead of to the prototype,
then we would only be able to call that function using MyClass.doSomethingSpecial()
.
Functions are objects after all and the instances don’t check their constructor function for properties they don’t have.
We have to add the method to MyClass
‘s prototype so new
instances of MyClass
can access it through
the prototype chain.
And what about inheritance?
This is where the syntax gets really gruesome:
function MyClass() {
SomeConstructor.apply(this, arguments);
}
MyClass.prototype = Object.create(SomeConstructor.prototype);
MyClass.prototype.constructor = MyClass;
In the body of our constructor, we optionally call the constructor function of
the SomeConstructor
class and bind it to the current scope’s this
value (ie: the object we are constructing).
We use Function.prototype.apply for that.
This would be similar to a child class calling the super()
keyword in Java.
The next lines run immediately as they are assignments and not function definitions.
We create a new prototype for MyClass
, using Object.create
.
This new prototype will have its own prototype set to whatever the first argument of Object.create
is
which in this case is SomeConstructor.prototype
.
This has the effect of allowing objects created with MyClass
to query SomeConstructor.prototype
for
properties in addition to SomeConstructor.prototype.prototype
and so on.
I like to think of this like smashing an amino acid chain into two parts and connecting one part to another chain and discarding the other.
Before inheritance
After inheritance
The last line sets the constructor property of MyClass
back to MyClass
.
I’ve noticed that if you don’t do this, uncaught errors show up as Object #SomeConstructor
rather
than Object #MyClass
which can be misleading.
Now all this gets tiresome quickly, especially if your code base deals with a lot of classes.
It should be mentioned that there are libraries out there that handle these details for you.
Node.js has the util.inherit
function built in.
Convenient, but it makes code less obvious if you ask me.
Why all this torment?
As far as I can tell, JavaScript was not meant to be used this way. It was after all influenced by Scheme.
If you have ever tried one of the Lisp dialects, they are a joy once you get the paradigms down. I still find it hard to model ‘business-logic’ in them however. I think that takes a lot of time and practice to get right.
Send in the Classes!
The examples from before in ES6 syntax look like this:
//class
class MyClass {
doSomethingSpecial() {
}
}
//Inheritance
class MyClass extends SomeConstructor {
constructor() {
super()
}
doSomethingSpecial() {
}
}
That’s it. Much less work and certainly easier on the eyes.
I’ve even started writing interfaces in this syntax and using jsdoc to generate useful documentation.
Now Browser support for classes is almost non-existent and it’s the same on the server with Node.js/io.js.
Screen grab from MDN
But fear not!
Thanks to excellent transpilers like babel you can use these features with little fuss on both the client and server side.
That is of course if you don’t mind the extra build step.
The new ES6 class syntax may not be for everyone but it certainly has been working for me. Do you tend to use factory methods, adapters and other patterns in your code?
ES6 classes makes that stuff a lot less cumbersome.