Today this study is about the latest ES6 Classes. This new construct is a part of Ecmascript 2015 standard, which has great potential on reducing boilerplate and also some small misconceptions.
First we have the old style classes. Before ES6, functions are at the same time constructors. It is possible to call ordinary function as constructors (as it is possible to have functions with no presence of [[Construct]] interface), and internally there is a special routine to handle constructing calls. Normally if we want to call function as constructor, we call with an operator ‘new’.
function ClassA () {}
new ClassA();
This time in ES 6, we explicitly declare ClassA as an empty class with a constructor. With the keyword ‘class’:
class ClassA {
constructor() {} // that is equivalent to function ClassA() {}
}
new ClassA();
The difference here is that ClassA now have no [[Call]] interface internally. :O. That means you cannot call ClassA as a normal function, which may enable you to use ClassA to extend existing instances. This is a point we will talk about later.
Prototype methods can be placed directly inside the class declaration. This remove even more boilerplate like ClassA.prototype.* .
class ClassA {
constructor() {}
sayHi() {console.log(‘Hi’);}
}
Inheritance is even more easier. Previous ES versions has to assign prototype chain manually to Object.create(). If there is a class ClassB inheriting from ClassA, we need to do the following:
function ClassB () {} // ClassB constructor
ClassB.prototype = Object.create(ClassA.prototype); // new instance of the prototype template
ClassB.prototype.constructor = ClassB; // correction on constructor pointer
With ‘extends’ keyword, we simply put:
class ClassB extends ClassA {
constructor () {}
}
Calls to superclass constructors and methods are much easier, without use of Function.prototype.call/apply for setting the context and explicit reference to the prototype of the superclass.
class ClassB extends ClassA {
constructor() {
super(); // necessary here in order to use ‘this’ in later part of constructor; note that this call to ClassA has context properly assigned to ‘this’ and [[Construct]] interface is used instead of [[Call]], in contrast to the old way to call super constructor: ClassA.call(this);
}
sayMoreHi() {
super.sayHi(); // here ‘this’ is also properly assign at the time of call
}
}
The nice part of this new syntax is that we get rid of a lot of boilerplate and call the functions with correct interface ([[Construct]] instead of [[Call]]). It abstracts the prototype mechanism to a more readable and friendly format.
New learners might find this syntax largely departs from the usual functional approach to instantiation of classes. They will not be able to see how prototype chain works under the class syntax. This is where misconceptions may come in. I would argue that this departure is necessary in learning another important internal mechanism of function application. One will have to distinguish what function can be call directly and others must be call as constructors. Examples are Document, Window, SVGElement and more.
I will foresee some difficulties in understanding patterns like factory and delegation, but these concepts do not come free in the first place even before ES6 classes are introduced. One must understand the mechanism of closure and environment- which still applies to ES6 classes- before talking about patterns. Introduction of classes should be welcomed in teaching closure, environment and eventually prototype chaining.