A class and a subclass:
Using the classes:
Under the hood, ES6 classes are not something that is radically new: They mainly provide more convenient syntax to create old-school constructor functions. You can see that if you use
A class is defined like this in ECMAScript 6:
You use this class just like an ES5 constructor function:
In fact, the result of a class definition is a function:
However, you can only invoke a class via
new, not via a function call (the rationale behind this is explained later):
There is no separating punctuation between the members of a class definition. For example, the members of an object literal are separated by commas, which are illegal at the top levels of class definitions. Semicolons are allowed, but ignored:
Semicolons are allowed in preparation for future syntax which may include semicolon-terminated members. Commas are forbidden to emphasize that class definitions are different from object literals.
Function declarations are hoisted: When entering a scope, the functions that are declared in it are immediately available – independently of where the declarations happen. That means that you can call a function that is declared later:
In contrast, class declarations are not hoisted. Therefore, a class only exists after execution reached its definition and it was evaluated. Accessing it beforehand leads to a
The reason for this limitation is that classes can have an
extends clause whose value is an arbitrary expression. That expression must be evaluated in the proper “location”, its evaluation can’t be hoisted.
Not having hoisting is less limiting than you may think. For example, a function that comes before a class declaration can still refer to that class, but you have to wait until the class declaration has been evaluated before you can call the function.
Similarly to functions, there are two kinds of class definitions, two ways to define a class: class declarations and class expressions.
Similarly to function expressions, class expressions can be anonymous:
Also similarly to function expressions, class expressions can have names that are only visible inside them:
The last two lines demonstrate that
Me does not become a variable outside of the class, but can be used inside it.
A class body can only contain methods, but not data properties. Prototypes having data properties is generally considered an anti-pattern, so this just enforces a best practice.
constructor, static methods, prototype methods
Let’s examine three kinds of methods that you often find in class definitions.
The object diagram for this class declaration looks as follows. Tip for understanding it:
[[Prototype]] is an inheritance relationship between objects, while
prototype is a normal property whose value is an object. The property
prototype is only special w.r.t. the
new operator using its value as the prototype for instances it creates.
First, the pseudo-method
constructor. This method is special, as it defines the function that represents the class:
It is sometimes called a
class constructor. It has features that normal constructor functions don’t have (mainly the ability to constructor-call its superconstructor via
super(), which is explained later).
Second, static methods. Static properties (or class properties) are properties of
Foo itself. If you prefix a method definition with
static, you create a class method:
Third, prototype methods. The prototype properties of
Foo are the properties of
Foo.prototype. They are usually methods and inherited by instances of
For the sake of finishing ES6 classes in time, they were deliberately designed to be “maximally minimal”. That’s why you can currently only create static methods, getters, and setters, but not static data properties. There is a proposal for adding them to the language. Until that proposal is accepted, there are two work-arounds that you can use.
First, you can manually add a static property:
You could use
Object.defineProperty() to create a read-only property, but I like the simplicity of an assignment.
Second, you can create a static getter:
In both cases, you get a property
Point.ZERO that you can read. In the first case, the same instance is returned every time. In the second case, a new instance is returned every time.
The syntax for getters and setters is just like in ECMAScript 5 object literals:
MyClass as follows.
You can define the name of a method via an expression, if you put it in square brackets. For example, the following ways of defining
Foo are all equivalent.
Several special methods in ECMAScript 6 have keys that are symbols. Computed method names allow you to define such methods. For example, if an object has a method whose key is
Symbol.iterator, it is iterable. That means that its contents can be iterated over by the
for-of loop and other language mechanisms.
If you prefix a method definition with an asterisk (
*), it becomes a generator method. Among other things, a generator is useful for defining the method whose key is
Symbol.iterator. The following code demonstrates how that works.
extends clause lets you create a subclass of an existing constructor (which may or may not have been defined via a class):
Again, this class is used like you’d expect:
There are two kinds of classes:
Pointis a base class, because it doesn’t have an
ColorPointis a derived class.
There are two ways of using
constructorin a class definition) uses it like a function call (
super(···)), in order to make a superconstructor call (line A).
static) use it like property references (
super.prop) or method calls (
super.method(···)), in order to refer to superproperties (line B).
The prototype of a subclass is the superclass in ECMAScript 6:
That means that static properties are inherited:
You can even super-call static methods:
In a derived class, you must call
super() before you can use
Implicitly leaving a derived constructor without calling
super() also causes an error:
Just like in ES5, you can override the result of a constructor by explicitly returning an object:
If you do so, it doesn’t matter whether
this has been initialized or not. In other words: you don’t have to call
super() in a derived constructor if you override the result in this manner.
If you don’t specify a
constructor for a base class, the following definition is used:
For derived classes, the following default constructor is used:
In ECMAScript 6, you can finally subclass all built-in constructors (there are work-arounds for ES5, but these have significant limitations).
For example, you can now create your own exception classes (that will inherit the feature of having a stack trace in most engines):
You can also create subclasses of
Array whose instances properly handle
Note that subclassing
Array is usually not the best solution. It’s often better to create your own class (whose interface you control) and to delegate to an Array in a private property.
This section explains four approaches for managing private data for ES6 classes:
Approaches #1 and #2 were already common in ES5, for constructors. Approaches #3 and #4 are new in ES6. Let’s implement the same example four times, via each of the approaches.
Our running example is a class
Countdown that invokes a callback
action once a counter (whose initial value is
counter) reaches zero. The two parameters
counter should be stored as private data.
In the first implementation, we store
Countdown looks like this:
The following code keeps private data in properties whose names a marked via a prefixed underscore:
There is a neat technique involving WeakMaps that combines the advantage of the first approach (safety) with the advantage of the second approach (being able to use prototype methods). This technique is demonstrated in the following code: we use the WeakMaps
_action to store private data.
Each of the two WeakMaps
_action maps objects to their private data. Due to how WeakMaps work that won’t prevent objects from being garbage-collected. As long as you keep the WeakMaps hidden from the outside world, the private data is safe.
If you want to be even safer, you can store
WeakMap.prototype.set in variables and invoke those (instead of the methods, dynamically):
Then your code won’t be affected if malicious code replaces those methods with ones that snoop on our private data. However, you are only protected against code that runs after your code. There is nothing you can do if it runs before yours.
Another storage location for private data are properties whose keys are symbols:
Each symbol is unique, which is why a symbol-valued property key will never clash with any other property key. Additionally, symbols are somewhat hidden from the outside world, but not completely:
instanceof) is also an instance of the superclass. The expectation is that subclass instances behave like superclass instances, but may do more.
The usefulness of classes for implementation inheritance is limited, because they only support single inheritance (a class can have at most one superclass). Therefore, it is impossible to inherit tool methods from multiple sources – they must all come from the superclass.
So how can we solve this problem? Let’s explore a solution via an example. Consider a management system for an enterprise where
Employee is a subclass of
Additionally, there are tool classes for storage and for data validation:
It would be nice if we could include the tool classes like this:
That is, we want
Employee to be a subclass of
Storage which should be a subclass of
Validation which should be a subclass of
Person will only be used in one such chain of classes. But
Validation will be used multiple times. We want them to be templates for classes whose superclasses we fill in. Such templates are called abstract subclasses or mixins.
One way of implementing a mixin in ES6 is to view it as a function whose input is a superclass and whose output is a subclass extending that superclass:
Here, we profit from the operand of the
extends clause not being a fixed identifier, but an arbitrary expression. With these mixins,
Employee is created like this:
Acknowledgement. The first occurrence of this technique that I’m aware of is a Gist by Sebastian Markbåge.
What we have seen so far are the essentials of classes. You only need to read on if you are interested how things happen under the hood. Let’s start with the syntax of classes. The following is a slightly modified version of the syntax shown in Sect. A.4 of the ECMAScript 6 specification.
arguments; duplicate class element names are not allowed; the name
constructorcan only be used for a normal method, not for a getter, a setter or a generator method.
TypeExceptionif they are.
Class declarations create (mutable) let bindings. The following table describes the attributes of properties related to a given class
Classes have lexical inner names, just like named function expressions.
You may know that named function expressions have lexical inner names:
me of the named function expression becomes a lexically bound variable that is unaffected by which variable currently holds the function.
Interestingly, ES6 classes also have lexical inner names that you can use in methods (constructor methods and regular methods):
(In the ES6 spec the inner name is set up by the dynamic semantics of ClassDefinitionEvaluation.)
Acknowledgement: Thanks to Michael Ficarra for pointing out that classes have inner names.
In ECMAScript 6, subclassing looks as follows.
The next section examines the structure of the objects that were created by the previous example. The section after that examines how
jane is allocated and initialized.
The previous example creates the following objects.
Prototype chains are objects linked via the
[[Prototype]] relationship (which is an inheritance relationship). In the diagram, you can see two prototype chains:
The prototype of a derived class is the class it extends. The reason for this setup is that you want a subclass to inherit all properties of its superclass:
The prototype of a base class is
Function.prototype, which is also the prototype of functions:
That means that base classes and all their derived classes (their prototypees) are functions. Traditional ES5 functions are essentially base classes.
The main purpose of a class is to set up this prototype chain. The prototype chain ends with
Object.prototype (whose prototype is
null). That makes
Object an implicit superclass of every base class (as far as instances and the
instanceof operator are concerned).
The reason for this setup is that you want the instance prototype of a subclass to inherit all properties of the superclass instance prototype.
As an aside, objects created via object literals also have the prototype
The data flow between class constructors is different from the canonical way of subclassing in ES5. Under the hood, it roughly looks as follows.
The instance object is created in different locations in ES6 and ES5:
super(), which triggers a constructor call.
new, the first in a chain of constructor calls. The superconstructor is invoked via a function call.
The previous code uses two new ES6 features:
new.targetis an implicit parameter that all functions have. In a chain of constructor calls, its role is similar to
thisin a chain of supermethod calls.
new(as in line B), the value of
new.targetis that constructor.
super()(as in line A), the value of
new.targetof the constructor that makes the call.
undefined. That means that you can use
new.targetto determine whether a function was function-called or constructor-called (via
new.targetrefers to the
new.targetof the surrounding non-arrow function.
Reflect.construct()lets you make constructor calls while specifying
new.targetvia the last parameter.
The advantage of this way of subclassing is that it enables normal code to subclass built-in constructors (such as
Array). A later section explains why a different approach was necessary.
As a reminder, here is how you do subclassing in ES5:
thisoriginally being uninitialized in derived constructors means that an error is thrown if they access
thisin any way before they have called
thisis initialized, calling
ReferenceError. This protects you against calling
returnstatement), the result is
thisis uninitialized, a
ReferenceErroris thrown. This protects you against forgetting to call
null), the result is
this(this behavior is required to remain compatible with ES5 and earlier). If
thisis uninitialized, a
thisis initialized or not.
Let’s examine how the
extends clause influences how a class is set up (Sect. 14.5.14 of the spec).
The value of an
extends clause must be “constructible” (invocable via
null is allowed, though.
Function.prototype(like a normal function)
Object.prototype(which is also the prototype of objects created via object literals)
Note the following subtle difference with the first case: If there is no
extends clause, the class is a base class and allocates instances. If a class extends
Object, it is a derived class and
Object allocates the instances. The resulting instances (including their prototype chains) are the same, but you get there differently.
Such a class lets you avoid
Object.prototype in the prototype chain.
In ECMAScript 5, most built-in constructors can’t be subclassed (several work-arounds exist).
To understand why, let’s use the canonical ES5 pattern to subclass
Array. As we shall soon find out, this doesn’t work.
Unfortunately, if we instantiate
MyArray, we find out that it doesn’t work properly: The instance property
length does not change in reaction to us adding Array elements:
There are two obstracles that prevent
myArr from being a proper Array.
First obstacle: initialization. The
this you hand to the constructor
Array (in line A) is completely ignored. That means you can’t use
Array to set up the instance that was created for
Second obstacle: allocation. The instance objects created by
Array are exotic (a term used by the ECMAScript specification for objects that have features that normal objects don’t have): Their property
length tracks and influences the management of Array elements. In general, exotic objects can be created from scratch, but you can’t convert an existing normal object into an exotic one. Unfortunately, that is what
Array would have to do, when called in line A: It would have to turn the normal object created for
MyArray into an exotic Array object.
In ECMAScript 6, subclassing
Array looks as follows:
Let’s examine how the ES6 approach to subclassing removes the previously mentioned obstacles:
Arraynot being able to set up an instance, is removed by
Arrayreturning a fully configured instance. In contrast to ES5, this instance has the prototype of the subclass.
The following ES6 code makes a supermethod call in line B.
To understand how super-calls work, let’s look at the object diagram of
In line B,
Employee.prototype.toString makes a super-call (line B) to the method (starting in line A) that it has overridden. Let’s call the object, in which a method is stored, the home object of that method. For example,
Employee.prototype is the home object of
The super-call in line B involves three steps:
toString. That method may be found in the object where the search started or later in the prototype chain.
this. The reason for doing so is: the super-called method must be able to access the same instance properties (in our example, the own properties of
Note that even if you are only getting (
super.prop) or setting (
super.prop = 123) a superproperty (versus making a method call),
this may still (internally) play a role in step #3, because a getter or a setter may be invoked.
Let’s express these steps in three different – but equivalent – ways:
Variation 3 is how ECMAScript 6 handles super-calls. This approach is supported by two internal bindings that the environments of functions have (environments provide storage space, so-called bindings, for the variables in a scope):
[[thisValue]]: This internal binding also exists in ECMAScript 5 and stores the value of
[[HomeObject]]: Refers to the home object of the environment’s function. Filled in via an internal property
[[HomeObject]]that all methods have that use
super. Both the binding and the property are new in ECMAScript 6.
Referring to superproperties is handy whenever prototype chains are involved, which is why you can use it in method definitions (incl. generator method definitions, getters and setters) inside object literals and class definitions. The class can be derived or not, the method can be static or not.
super to refer to a property is not allowed in function declarations, function expressions and generator functions.
supercan’t be moved
You can’t move a method that uses
super: Such a method has an internal property
[[HomeObject]] that ties it to the object it was created in. If you move it via an assignment, it will continue to refer to the superproperties of the original object. In future ECMAScript versions, there may be a way to transfer such a method, too.
One more mechanism of built-in constructors has been made extensible in ECMAScript 6: Sometimes a method creates new instances of its class. If you create a subclass – should the method return an instance of its class or an instance of the subclass? A few built-in ES6 methods let you configure how they create instances via the so-called species pattern.
As an example, consider a subclass
Array. If we invoke
map() on instances of that class, we want it to return instances of
Array, to avoid unnecessary sorting. By default,
map() returns instances of the receiver (
this), but the species patterns lets you change that.
In the following three sections, I’ll use two helper functions in the examples:
The standard species pattern is used by
filter() method of Typed Arrays and other operations. It works as follows:
this.constructor[Symbol.species]exists, use it as a constructor for the new instance.
Normal Arrays implement the species pattern slightly differently:
Array.prototype.map() creates the Array it returns via
Promises use a variant of the species pattern for static methods such as
This is the default getter for the property
This default getter is implemented by the built-in classes
%TypedArray%. It is automatically inherited by subclasses of these built-in classes.
There are two ways in which you can override the default species: with a constructor of your choosing or with
You can override the default species via a static getter (line A):
As a result,
map() returns an instance of
If you don’t override the default species,
map() returns an instance of the subclass:
If you don’t want to use a static getter, you need to use
Object.defineProperty(). You can’t use assignment, as there is already a property with that key that only has a getter. That means that it is read-only and can’t be assigned to.
For example, here we set the species of
If you set the species to
null then the default constructor is used (which one that is depends on which variant of the species pattern is used, consult the previous sections for more information).
ES6 classes provide a few clear benefits:
Let’s look at a few common complaints about ES6 classes. You will see me agree with most of them, but I also think that they benefits of classes much outweigh their disadvantages. I’m glad that they are in ES6 and I recommend to use them.
Proto.js project, via a tiny library (which proves how good a fit this approach is).
However, backwards-compatibility matters, which is why classes being constructor functions also makes sense. That way, ES6 code and ES5 are more interoperable.
The disconnect between syntax and semantics will cause some friction in ES6 and later. But you can lead a comfortable life by simply taking ES6 classes at face value. I don’t think the illusion will ever bite you. Newcomers can get started more quickly and later read up on what goes on behind the scenes (after they are more comfortable with the language).
Classes only give you single inheritance, which severely limits your freedom of expression w.r.t. object-oriented design. However, the plan has always been for them to be the foundation of a multiple-inheritance mechanism such as traits.
Then a class becomes an instantiable entity and a location where you assemble traits. Until that happens, you will need to resort to libraries if you want multiple inheritance.
If you want to instantiate a class, you are forced to use
new in ES6. That means that you can’t switch from a class to a factory function without changing the call sites. That is indeed a limitation, but there are two mitigating factors:
newoperator, by returning an object from the
constructormethod of a class.
newto a function call will be simple. Obviously that doesn’t help you if you don’t control the code that calls your code, as is the case for libraries.
Function-calling classes is currently forbidden. That was done to keep options open for the future, to eventually add a way to handle function calls via classes.
What is the analog of
Function.prototype.apply() for classes? That is, if I have a class
TheClass and an Array
args of arguments, how do I instantiate
One way of doing so is via the spread operator (
Another option is to use
The design motto for classes was “maximally minimal”. Several advanced features were discussed, but ultimately discarded in order to get a design that would be unanimously accepted by TC39.
Upcoming versions of ECMAScript can now extend this minimal design – classes will provide a foundation for features such as traits (or mixins), value objects (where different objects are equal if they have the same content) and const classes (that produce immutable instances).
The following document is an important source of this chapter: