Published: 2017-12-03
Updated: 2018-01-13
Web: http://fritzthecat-blog.blogspot.com/2017/12/es6-classes.html
ES6 classes provide a syntax that is much more elegant than all the JavaScript inheritance workarounds.
We have the class
keyword to define classes, and the extends
keyword for inheritance.
The class-constructor is a method named by the keyword constructor
.
Accessing super-classes is possible via the super
keyword.
Methods are written like
functions in objects,
just name and parameter list, nothing else.
Here is an example:
class Being { constructor(species) { this.species = species } toString() { return this.species } } class Animal extends Being { constructor(species) { super(species) } eat(food) { this.food = food } toString() { return super.toString()+" ("+this.food+")" } } const gorilla = new Animal("Gorilla") gorilla.eat("Banana") alert(gorilla)
Mind that I write classes with a newline before the opening curly brace. This makes them easier distinguishable from functions.
ES6 classes provide single inheritance,
just one class name is possible after the extends
keyword.
That means a class can not inherit from more than one super-class.
Nevertheless multiple inheritance is possible via
mixins.
super()
in its constructor
(although this doesn't need to be the first call, see example below).
button.addEventListener("click", callbackObject.click)
this
pointer in callbackObject.click()
implementation. this
is always what is left of the dot,
and in such a case this
would be the button
, not the callbackObject
!
Click onto one of the buttons to get an example script into the text area. Below the script you find an explanation.
So are we happy now? Is ES6 now a full-featured OO language?
ES6 still does not provide function-overloading, meaning you can not have two methods with the same name in a class:
1 2 3 4 5 6 7 8 9 10 11 12 | class Foo { bar(message) { alert("Message bar() with "+message) } bar() { alert("Default bar()") } } const foo = new Foo() foo.bar("Hello Function Overloading") |
The code above yields:
Default bar()
Just the last bar()
survives, and you don't get any error message from the interpreter!
ES6 classes do not allow private properties in a way we would expect. Everything is public and overwritable.
Thus writing code like the following would give you an interpreter error "SyntaxError: missing : after property id"
on line 3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Animal { let eatenFood = "none" /* syntax error! */ constructor(species, food) { this.species = species eatenFood = food } eat(food) { eatenFood = food } toString() { return this.species+" ("+eatenFood+")" } } |
When someone told you that the new ES6 Symbol
lets you create hidden properties, that's not true.
True is that code which doesn't know the symbol will not be able to read the property.
Consider following example with a symbol-property:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class Animal { constructor(name) { this.name = name this.eatenFood = Symbol("eatenFood") /* define a symbol-property */ } eat(food) { this[this.eatenFood] = food } } const garfield = new Animal("Garfield") garfield.eat("mouse") if (garfield[garfield.eatenFood] === "mouse") alert("Property 'eatenFood' can be read, it is not private") garfield[garfield.eatenFood] = "cheese" if (garfield[garfield.eatenFood] === "cheese") alert("Property 'eatenFood' can also be written") |
The code above alerts
Property 'eatenFood' can be read, it is not private Property 'eatenFood' can also be written
Thus the symbol-property is neither private nor can it protect its value from being overwritten by code that has access to the symbol.
Not even getters and setters, as introduced in ES5, can protect properties.
Getters provide a custom way (computations) to read a property, setters do the same for writing to it. So, leaving out the setter should make the property read-only? Actually yes, but that can not prevent direct access of the underlying property, which must have another name than the getter-property, and which is automatically public in ES6 classes. Look at following example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | class Point { constructor(x, y) { this.myX = x this.myY = y } get x() { return this.myX } get y() { return this.myY } } const point = new Point(1, 2) point.x = 3 point.y = 4 alert("Point is: "+point.x+" / "+point.y) point.myX = 5 point.myY = 6 alert("Point is: "+point.x+" / "+point.y) |
This example defines a class with getters, leaving out the setters to make the "properties" x
and y
read-only.
But that can not prevent bogus code from accessing the properties directly,
as we see in the assignment of myX
and myY
.
Above example alerts
Point is: 1 / 2 Point is: 5 / 6
So immutable classes are out of reach with ES6?
You want to know why Point
needs to be immutable?
Ask the Java AWT and Swing developers.
They really suffered from java.awt.Point being mutable.
The fatality unfolded after years only, when it was much too late to take it back.
The ES6 class syntax is much more concise and elegant than that of JavaScript. This is a big step into future. But resilience is still lacking, most likely because encapsulation mechanisms are hard to introduce when needing to stay backward-compatible with JS. From that point of view, ES6 classes look more like surface polishing.
As I'm an advocate of functional inheritance, I must say that this has not lost anything of its beauty. It provides private functions and properties, better suited for encapsulation than ES6 classes!
This is the old way to create classes and sub-classes in JavaScript.
See also Mozilla inheritance reference.
A class is a function, called via the new
operator.
That function is also the constructor of the class.
It stores the received construction-parameters to the instance-bound this
object.
This example defines an inheritance hierarchy of three levels.
A sub-class must call the super-constructor via SuperClass.call(this, ....)
.
Further it must perform two prototype
creation calls.
Instance functions should not be defined in constructor but be attached
after preparing the prototype
.
Mind that you can't have private variables in such "classes" - where would you put them
when all methods are defined outside the constructor function?
This example defines an inheritance hierarchy of three levels,
an Animal
is a specialization of a Being
, and a
Cat
is a specialization of an Animal
.
Compare this ES6 inheritance with the JavaScript "Old Prototypal Inheritance" example: ES6 is much more concise!
Mind that the this
keyword still is mandatory when referencing class-properties and -methods,
and also mind that still everything is public and overwritable.
This example defines an inheritance hierarchy of two levels,
featuring two different sub-classes that create instances of their own class.
The base class Animal defines the ability to reproduce
by calling an "abstract" method newInstance()
,
which must be defined by sub-classes.
Mind that both class-methods and -properties must be called using the this
prefix!
The two sub-class implementations both return a specialized new instance of their class,
as it is asserted via instanceof
.
Mind that both newInstance()
and reproduce()
need to be public,
although newInstance()
should be protected (not callable from outside).
ES6 does not provide access modifiers.
The class Point
should make it impossible to change x
or y
coordinates.
This is called an immutable object, which is the resilient functional programming style.
Thus we provide only getter
properties for y
and y
, and leave out the setters.
However we want the point to be able to belong to different shapes,
and thus we provide also a setter
for property owner
.
Assigning a new value now to property x
or y
doesn't cause an exception, but it is ignored
(which is a questionable behavior).
Nevertheless assigning a value directly to the properties myX
and myY
is still possible,
so getters are just a new feature, not a protection for private properties!
Giving the property owner
a new value is provided by the presence of a setter.
Other than in Java you can make calls in constructor before the super()
call.
What you can't do is call an instance method in constructor before super()
.
Try it out by in-commenting this.growl()
.
ES6 introduced the static
keyword for defining methods that should not see the this
instance pointer.
Typically these would be functionality that deals with several instances,
and you would like to put that into the class, because it belongs there.
This example provides a static clone()
method that encapsulates
the process of copying an Animal
instance. The instances should have the same birthDate
,
so we must reset the birthDate
that is set automatically in the constructor.
Mind that you can not have static properties.
You would get the interpreter error "bad method definition".
ɔ⃝ Fritz Ritzberger, 2017-12-03