newA class and a subclass:
classPoint{constructor(x,y){this.x=x;this.y=y;}toString(){return`(${this.x},${this.y})`;}}classColorPointextendsPoint{constructor(x,y,color){super(x,y);this.color=color;}toString(){returnsuper.toString()+' in '+this.color;}}
Using the classes:
> const cp = new ColorPoint(25, 8, 'green');> cp.toString();'(25, 8) in green'> cp instanceof ColorPointtrue> cp instanceof PointtrueUnder 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 usetypeof:
> typeof Point'function'A class is defined like this in ECMAScript 6:
classPoint{constructor(x,y){this.x=x;this.y=y;}toString(){return`(${this.x},${this.y})`;}}
You use this class just like an ES5 constructor function:
> var p = new Point(25, 8);> p.toString()'(25, 8)'In fact, the result of a class definition is a function:
> typeof Point'function'However, you can only invoke a class vianew, not via a function call (the rationale behind thisis explained later):
> Point()TypeError: Classes can’t be function-calledIn the spec, function-calling classes is prevented inthe internal method[[Call]] of function objects.
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:
classMyClass{foo(){};// OK, ignored,// SyntaxErrorbar(){}}
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 arehoisted: 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:
foo();// works, because `foo` is hoistedfunctionfoo(){}
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 aReferenceError:
newFoo();// ReferenceErrorclassFoo{}
The reason for this limitation is that classes can have anextends 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.
functionfunctionThatUsesBar(){newBar();}functionThatUsesBar();// ReferenceErrorclassBar{}functionThatUsesBar();// OK
Similarly to functions, there are two kinds ofclass definitions, two ways to define a class:class declarations andclass expressions.
Similarly to function expressions, class expressions can be anonymous:
constMyClass=class{···};constinst=newMyClass();
Also similarly to function expressions, class expressions can have names that are only visible inside them:
constMyClass=classMe{getClassName(){returnMe.name;}};constinst=newMyClass();console.log(inst.getClassName());// Meconsole.log(Me.name);// ReferenceError: Me is not defined
The last two lines demonstrate thatMe 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 methodsLet’s examine three kinds of methods that you often find in class definitions.
classFoo{constructor(prop){this.prop=prop;}staticstaticMethod(){return'classy';}prototypeMethod(){return'prototypical';}}constfoo=newFoo(123);
The object diagram for this class declaration looks as follows. Tip for understanding it:[[Prototype]] is an inheritance relationship between objects, whileprototype is a normal property whose value is an object. The propertyprototype is only special w.r.t. thenew operator using its value as the prototype for instances it creates.

First, the pseudo-methodconstructor. This method is special, as it defines the function that represents the class:
> Foo === Foo.prototype.constructortrue> typeof Foo'function'It is sometimes called aclass constructor. It has features that normal constructor functions don’t have (mainly the ability to constructor-call its superconstructor viasuper(), which is explained later).
Second, static methods.Static properties (orclass properties) are properties ofFoo itself. If you prefix a method definition withstatic, you create a class method:
> typeof Foo.staticMethod'function'> Foo.staticMethod()'classy'Third, prototype methods. Theprototype properties ofFoo are the properties ofFoo.prototype. They are usually methods and inherited by instances ofFoo.
> typeof Foo.prototype.prototypeMethod'function'> foo.prototypeMethod()'prototypical'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:
classPoint{constructor(x,y){this.x=x;this.y=y;}}Point.ZERO=newPoint(0,0);
You could useObject.defineProperty() to create a read-only property, but I like the simplicity of an assignment.
Second, you can create a static getter:
classPoint{constructor(x,y){this.x=x;this.y=y;}staticgetZERO(){returnnewPoint(0,0);}}
In both cases, you get a propertyPoint.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 likein ECMAScript 5 object literals:
classMyClass{getprop(){return'getter';}setprop(value){console.log('setter: '+value);}}
You useMyClass as follows.
> const inst = new MyClass();> inst.prop = 123;setter: 123> inst.prop'getter'You can define the name of a method via an expression, if you put it in square brackets. For example, the following ways of definingFoo are all equivalent.
classFoo{myMethod(){}}classFoo{['my'+'Method'](){}}constm='myMethod';classFoo{[m](){}}
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 isSymbol.iterator, it isiterable. That means that its contents can be iterated over by thefor-of loop and other language mechanisms.
classIterableClass{[Symbol.iterator](){···}}
If you prefix a method definition with an asterisk (*), it becomes agenerator method. Among other things, a generator is useful for defining the method whose key isSymbol.iterator. The following code demonstrates how that works.
classIterableArguments{constructor(...args){this.args=args;}*[Symbol.iterator](){for(constargofthis.args){yieldarg;}}}for(constxofnewIterableArguments('hello','world')){console.log(x);}// Output:// hello// world
Theextends clause lets you create a subclass of an existing constructor (which may or may not have been defined via a class):
classPoint{constructor(x,y){this.x=x;this.y=y;}toString(){return`(${this.x},${this.y})`;}}classColorPointextendsPoint{constructor(x,y,color){super(x,y);// (A)this.color=color;}toString(){returnsuper.toString()+' in '+this.color;// (B)}}
Again, this class is used like you’d expect:
> const cp = new ColorPoint(25, 8, 'green');> cp.toString()'(25, 8) in green'> cp instanceof ColorPointtrue> cp instanceof PointtrueThere are two kinds of classes:
Point is abase class, because it doesn’t have anextends clause.ColorPoint is aderived class.There are two ways of usingsuper:
constructor in 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:
> Object.getPrototypeOf(ColorPoint) === PointtrueThat means that static properties are inherited:
classFoo{staticclassMethod(){return'hello';}}classBarextendsFoo{}Bar.classMethod();// 'hello'
You can even super-call static methods:
classFoo{staticclassMethod(){return'hello';}}classBarextendsFoo{staticclassMethod(){returnsuper.classMethod()+', too';}}Bar.classMethod();// 'hello, too'
In a derived class, you must callsuper() before you can usethis:
classFoo{}classBarextendsFoo{constructor(num){consttmp=num*2;// OKthis.num=num;// ReferenceErrorsuper();this.num=num;// OK}}
Implicitly leaving a derived constructor without callingsuper() also causes an error:
classFoo{}classBarextendsFoo{constructor(){}}constbar=newBar();// ReferenceError
Just like in ES5, you can override the result of a constructor by explicitly returning an object:
classFoo{constructor(){returnObject.create(null);}}console.log(newFoo()instanceofFoo);// false
If you do so, it doesn’t matter whetherthis has been initialized or not. In other words: you don’t have to callsuper() in a derived constructor if you override the result in this manner.
If you don’t specify aconstructor for a base class, the following definition is used:
constructor(){}
For derived classes, the following default constructor is used:
constructor(...args){super(...args);}
In ECMAScript 6, you can finally subclass all built-in constructors (there arework-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):
classMyErrorextendsError{}thrownewMyError('Something happened!');
You can also create subclasses ofArray whose instances properly handlelength:
classStackextendsArray{gettop(){returnthis[this.length-1];}}varstack=newStack();stack.push('world');stack.push('hello');console.log(stack.top);// helloconsole.log(stack.length);// 2
Note that subclassingArray 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.
Subclassing built-in constructors is something that engines have to support natively, you won’t get this feature via transpilers.
This section explains four approaches for managing private data for ES6 classes:
constructorApproaches #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 classCountdown that invokes a callbackaction once a counter (whose initial value iscounter) reaches zero. The two parametersaction andcounter should be stored as private data.
In the first implementation, we storeaction andcounter in theenvironment of the class constructor. An environment is the internal data structure, in which a JavaScript engine stores the parameters and local variables that come into existence whenever a new scope is entered (e.g. via a function call or a constructor call). This is the code:
classCountdown{constructor(counter,action){Object.assign(this,{dec(){if(counter<1)return;counter--;if(counter===0){action();}}});}}
UsingCountdown looks like this:
> const c = new Countdown(2, () => console.log('DONE'));> c.dec();> c.dec();DONEPros:
Cons:
More information on this technique: Sect. “Private Data in the Environment of a Constructor (Crockford Privacy Pattern)” in “Speaking JavaScript”.
The following code keeps private data in properties whose names a marked via a prefixed underscore:
classCountdown{constructor(counter,action){this._counter=counter;this._action=action;}dec(){if(this._counter<1)return;this._counter--;if(this._counter===0){this._action();}}}
Pros:
Cons:
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_counter and_action to store private data.
const_counter=newWeakMap();const_action=newWeakMap();classCountdown{constructor(counter,action){_counter.set(this,counter);_action.set(this,action);}dec(){letcounter=_counter.get(this);if(counter<1)return;counter--;_counter.set(this,counter);if(counter===0){_action.get(this)();}}}
Each of the two WeakMaps_counter and_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 storeWeakMap.prototype.get andWeakMap.prototype.set in variables and invoke those (instead of the methods, dynamically):
constset=WeakMap.prototype.set;···set.call(_counter,this,counter);// _counter.set(this, counter);
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.
Pros:
Con:
Another storage location for private data are properties whose keys are symbols:
const_counter=Symbol('counter');const_action=Symbol('action');classCountdown{constructor(counter,action){this[_counter]=counter;this[_action]=action;}dec(){if(this[_counter]<1)return;this[_counter]--;if(this[_counter]===0){this[_action]();}}}
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:
constc=newCountdown(2,()=>console.log('DONE'));console.log(Object.keys(c));// []console.log(Reflect.ownKeys(c));// [ Symbol(counter), Symbol(action) ]
Pros:
Cons:
Reflect.ownKeys().Subclassing in JavaScript is used for two reasons:
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 whereEmployee is a subclass ofPerson.
classPerson{···}classEmployeeextendsPerson{···}
Additionally, there are tool classes for storage and for data validation:
classStorage{save(database){···}}classValidation{validate(schema){···}}
It would be nice if we could include the tool classes like this:
// Invented ES6 syntax:classEmployeeextendsStorage,Validation,Person{···}
That is, we wantEmployee to be a subclass ofStorage which should be a subclass ofValidation which should be a subclass ofPerson.Employee andPerson will only be used in one such chain of classes. ButStorage andValidation will be used multiple times. We want them to be templates for classes whose superclasses we fill in. Such templates are calledabstract subclasses ormixins.
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:
constStorage=Sup=>classextendsSup{save(database){···}};constValidation=Sup=>classextendsSup{validate(schema){···}};
Here, we profit from the operand of theextends clause not being a fixed identifier, but an arbitrary expression. With these mixins,Employee is created like this:
classEmployeeextendsStorage(Validation(Person)){···}
Acknowledgement. The first occurrence of this technique that I’m aware of isa 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 inSect. A.4 of the ECMAScript 6 specification.
ClassDeclaration: "class" BindingIdentifier ClassTailClassExpression: "class" BindingIdentifier? ClassTailClassTail: ClassHeritage? "{" ClassBody? "}"ClassHeritage: "extends" AssignmentExpressionClassBody: ClassElement+ClassElement: MethodDefinition "static" MethodDefinition ";"MethodDefinition: PropName "(" FormalParams ")" "{" FuncBody "}" "*" PropName "(" FormalParams ")" "{" GeneratorBody "}" "get" PropName "(" ")" "{" FuncBody "}" "set" PropName "(" PropSetParams ")" "{" FuncBody "}"PropertyName: LiteralPropertyName ComputedPropertyNameLiteralPropertyName: IdentifierName /* foo */ StringLiteral /* "foo" */ NumericLiteral /* 123.45, 0xFF */ComputedPropertyName: "[" Expression "]"Two observations:
classFooextendscombine(MyMixin,MySuperClass){}
eval orarguments; duplicate class element names are not allowed; the nameconstructor can only be used for a normal method, not for a getter, a setter or a generator method.TypeException if they are.classC{m(){}}newC.prototype.m();// TypeError
Class declarations create (mutable) let bindings. The following table describes the attributes of properties related to a given classFoo:
| writable | enumerable | configurable | |
|---|---|---|---|
Static propertiesFoo.* | true | false | true |
Foo.prototype | false | false | false |
Foo.prototype.constructor | false | false | true |
Prototype propertiesFoo.prototype.* | true | false | true |
Notes:
The properties shown in the table are created in Sect. “Runtime Semantics: ClassDefinitionEvaluation” in the spec.
Classes have lexical inner names, just like named function expressions.
You may know that named function expressions have lexical inner names:
constfac=functionme(n){if(n>0){// Use inner name `me` to// refer to functionreturnn*me(n-1);}else{return1;}};console.log(fac(3));// 6
The nameme 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):
classC{constructor(){// Use inner name C to refer to classconsole.log(`constructor:${C.prop}`);}logProp(){// Use inner name C to refer to classconsole.log(`logProp:${C.prop}`);}}C.prop='Hi!';constD=C;C=null;// C is not a class, anymore:newC().logProp();// TypeError: C is not a function// But inside the class, the identifier C// still worksnewD().logProp();// constructor: Hi!// logProp: Hi!
(In the ES6 spec the inner name is set up bythe dynamic semantics of ClassDefinitionEvaluation.)
Acknowledgement: Thanks to Michael Ficarra for pointing out that classes have inner names.
In ECMAScript 6, subclassing looks as follows.
classPerson{constructor(name){this.name=name;}toString(){return`Person named${this.name}`;}staticlogNames(persons){for(constpersonofpersons){console.log(person.name);}}}classEmployeeextendsPerson{constructor(name,title){super(name);this.title=title;}toString(){return`${super.toString()}(${this.title})`;}}constjane=newEmployee('Jane','CTO');console.log(jane.toString());// Person named Jane (CTO)
The next section examines the structure of the objects that were created by the previous example. The section after that examines howjane 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:
> Employee.logNames === Person.logNamestrueThe prototype of a base class isFunction.prototype, which is also the prototype of functions:
> const getProto = Object.getPrototypeOf.bind(Object);> getProto(Person) === Function.prototypetrue> getProto(function () {}) === Function.prototypetrueThat 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 withObject.prototype (whose prototype isnull). That makesObject an implicit superclass of every base class (as far as instances and theinstanceof 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 prototypeObject.prototype:
> Object.getPrototypeOf({}) === Object.prototypetrueThe data flow between class constructors is different from the canonical way of subclassing in ES5. Under the hood, it roughly looks as follows.
// Base class: this is where the instance is allocatedfunctionPerson(name){// Performed before entering this constructor:this=Object.create(new.target.prototype);this.name=name;}···functionEmployee(name,title){// Performed before entering this constructor:this=uninitialized;this=Reflect.construct(Person,[name],new.target);// (A)// super(name);this.title=title;}Object.setPrototypeOf(Employee,Person);···constjane=Reflect.construct(// (B)Employee,['Jane','CTO'],Employee);// const jane = new Employee('Jane', 'CTO')
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.target is an implicit parameter that all functions have. In a chain of constructor calls, its role is similar tothis in a chain of supermethod calls.new (as in line B), the value ofnew.target is that constructor.super() (as in line A), the value ofnew.target is thenew.target of the constructor that makes the call.undefined. That means that you can usenew.target to determine whether a function was function-called or constructor-called (vianew).new.target refers to thenew.target of the surrounding non-arrow function.Reflect.construct() lets you make constructor calls while specifyingnew.target via the last parameter.The advantage of this way of subclassing is that it enables normal code to subclass built-in constructors (such asError andArray). A later section explains why a different approach was necessary.
As a reminder, here is how you do subclassing in ES5:
functionPerson(name){this.name=name;}···functionEmployee(name,title){Person.call(this,name);this.title=title;}Employee.prototype=Object.create(Person.prototype);Employee.prototype.constructor=Employee;···
this originally being uninitialized in derived constructors means that an error is thrown if they accessthis in any way before they have calledsuper().this is initialized, callingsuper() produces aReferenceError. This protects you against callingsuper() twice.return statement), the result isthis. Ifthis is uninitialized, aReferenceError is thrown. This protects you against forgetting to callsuper().undefined andnull), the result isthis (this behavior is required to remain compatible with ES5 and earlier). Ifthis is uninitialized, aTypeError is thrown.this is initialized or not.extends clauseLet’s examine how theextends clause influences how a class is set up (Sect. 14.5.14 of the spec).
The value of anextends clause must be “constructible” (invocable vianew).null is allowed, though.
classC{}
C:Function.prototype (like a normal function)C.prototype:Object.prototype (which is also the prototype of objects created via object literals)classCextendsB{}
C:BC.prototype:B.prototypeclassCextendsObject{}
C:ObjectC.prototype:Object.prototypeNote the following subtle difference with the first case: If there is noextends clause, the class is a base class and allocates instances. If a class extendsObject, it is a derived class andObject allocates the instances. The resulting instances (including their prototype chains) are the same, but you get there differently.
classCextendsnull{}
C:Function.prototypeC.prototype:nullSuch a class lets you avoidObject.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 subclassArray. As we shall soon find out, this doesn’t work.
functionMyArray(len){Array.call(this,len);// (A)}MyArray.prototype=Object.create(Array.prototype);
Unfortunately, if we instantiateMyArray, we find out that it doesn’t work properly: The instance propertylength does not change in reaction to us adding Array elements:
> var myArr = new MyArray(0);> myArr.length0> myArr[0] = 'foo';> myArr.length0There are two obstracles that preventmyArr from being a proper Array.
First obstacle: initialization. Thethis you hand to the constructorArray (in line A) is completely ignored. That means you can’t useArray to set up the instance that was created forMyArray.
> var a = [];> var b = Array.call(a, 3);> a !== b // a is ignored, b is a new objecttrue> b.length // set up correctly3> a.length // unchanged0Second obstacle: allocation. The instance objects created byArray areexotic (a term used by the ECMAScript specification for objects that have features that normal objects don’t have): Their propertylength 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 whatArray would have to do, when called in line A: It would have to turn the normal object created forMyArray into an exotic Array object.
In ECMAScript 6, subclassingArray looks as follows:
classMyArrayextendsArray{constructor(len){super(len);}}
This works:
> const myArr = new MyArray(0);> myArr.length0> myArr[0] = 'foo';> myArr.length1Let’s examine how the ES6 approach to subclassing removes the previously mentioned obstacles:
Array not being able to set up an instance, is removed byArray returning 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.
classPerson{constructor(name){this.name=name;}toString(){// (A)return`Person named${this.name}`;}}classEmployeeextendsPerson{constructor(name,title){super(name);this.title=title;}toString(){return`${super.toString()}(${this.title})`;// (B)}}constjane=newEmployee('Jane','CTO');console.log(jane.toString());// Person named Jane (CTO)
To understand how super-calls work, let’s look at the object diagram ofjane:

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, thehome object of that method. For example,Employee.prototype is the home object ofEmployee.prototype.toString().
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 ofjane).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 1: supermethod calls in ES5varresult=Person.prototype.toString.call(this)// steps 1,2,3// Variation 2: ES5, refactoredvarsuperObject=Person.prototype;// step 1varsuperMethod=superObject.toString;// step 2varresult=superMethod.call(this)// step 3// Variation 3: ES6varhomeObject=Employee.prototype;varsuperObject=Object.getPrototypeOf(homeObject);// step 1varsuperMethod=superObject.toString;// step 2varresult=superMethod.call(this)// step 3
Variation 3 is how ECMAScript 6 handles super-calls. This approach is supported bytwo internalbindings that theenvironments of functions have (environments provide storage space, so-calledbindings, for the variables in a scope):
[[thisValue]]: This internal binding also exists in ECMAScript 5 and stores the value ofthis.[[HomeObject]]: Refers to the home object of the environment’s function. Filled in via the internal slot[[HomeObject]] that all methods have that usesuper. Both the binding and the slot are new in ECMAScript 6.In a class, a method definition that usessuper creates a special kind of function: It is still a function, but it has the internal slot[[HomeObject]]. That slot is set up by the method definition and can’t be changed in JavaScript. Therefore, you can’t meaningfully move such a method to a different object. (But maybe it’ll be possible in a future version of ECMAScript.)
super?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.
Usingsuper to refer to a property is not allowed in function declarations, function expressions and generator functions.
super can’t be movedYou can’t move a method that usessuper: Such a method has the internal slot[[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-calledspecies pattern.
As an example, consider a subclassSortedArray ofArray. If we invokemap() on instances of that class, we want it to return instances ofArray, 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:
functionisObject(value){return(value!==null&&(typeofvalue==='object'||typeofvalue==='function'));}/*** Spec-internal operation that determines whether `x`* can be used as a constructor.*/functionisConstructor(x){···}
The standard species pattern is used byPromise.prototype.then(), thefilter() 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.Array for Arrays).Implemented in JavaScript, the pattern would look like this:
functionSpeciesConstructor(O,defaultConstructor){constC=O.constructor;if(C===undefined){returndefaultConstructor;}if(!isObject(C)){thrownewTypeError();}constS=C[Symbol.species];if(S===undefined||S===null){returndefaultConstructor;}if(!isConstructor(S)){thrownewTypeError();}returnS;}
The standard species pattern is implemented in the spec via the operationSpeciesConstructor().
Normal Arrays implement the species pattern slightly differently:
functionArraySpeciesCreate(self,length){letC=undefined;// If the receiver `self` is an Array,// we use the species patternif(Array.isArray(self)){C=self.constructor;if(isObject(C)){C=C[Symbol.species];}}// Either `self` is not an Array or the species// pattern didn’t work out:// create and return an Arrayif(C===undefined||C===null){returnnewArray(length);}if(!IsConstructor(C)){thrownewTypeError();}returnnewC(length);}
Array.prototype.map() creates the Array it returns viaArraySpeciesCreate(this, this.length).
The species pattern for Arrays is implemented in the spec via the operationArraySpeciesCreate().
Promises use a variant of the species pattern for static methods such asPromise.all():
letC=this;// defaultif(!isObject(C)){thrownewTypeError();}// The default can be overridden via the property `C[Symbol.species]`constS=C[Symbol.species];if(S!==undefined&&S!==null){C=S;}if(!IsConstructor(C)){thrownewTypeError();}constinstance=newC(···);
This is the default getter for the property[Symbol.species]:
staticget[Symbol.species](){returnthis;}
This default getter is implemented by the built-in classesArray,ArrayBuffer,Map,Promise,RegExp,Set and%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 withnull.
You can override the default species via a static getter (line A):
classMyArray1extendsArray{staticget[Symbol.species](){// (A)returnArray;}}
As a result,map() returns an instance ofArray:
constresult1=newMyArray1().map(x=>x);console.log(result1instanceofArray);// true
If you don’t override the default species,map() returns an instance of the subclass:
classMyArray2extendsArray{}constresult2=newMyArray2().map(x=>x);console.log(result2instanceofMyArray2);// true
If you don’t want to use a static getter, you need to useObject.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 ofMyArray1 toArray:
Object.defineProperty(MyArray1,Symbol.species,{value:Array});
nullIf you set the species tonull 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).
classMyArray3extendsArray{staticget[Symbol.species](){returnnull;}}constresult3=newMyArray3().map(x=>x);console.log(result3instanceofArray);// true
Classes are controversial within the JavaScript community: On one hand, people coming from class-based languages are happy that they don’t have to deal with JavaScript’s unconventional inheritance mechanisms, anymore. On the other hand, there are many JavaScript programmers who argue that what’s complicated about JavaScript is not prototypal inheritance, but constructors.
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.
Yes, ES6 classes do obscure the true nature of JavaScript inheritance. There is an unfortunate disconnect between what a class looks like (its syntax) and how it behaves (its semantics): It looks like an object, but it is a function. My preference would have been for classes to beconstructor objects, not constructor functions. I explore that approach intheProto.js project, via a tiny library (which proves how good a fit this approach is).
However, backward-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.
Check outtraits.js if you are interested in how traits work (they are similar to mixins, which you may be familiar with).
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.
newIf you want to instantiate a class, you are forced to usenew 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:
new operator, by returning an object from theconstructor method of a class.new to 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.Therefore, classes dosomewhat limit you syntactically, but, once JavaScript has traits, they won’t limit youconceptually (w.r.t. object-oriented design).
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 ofFunction.prototype.apply() for classes? That is, if I have a classTheClass and an Arrayargs of arguments, how do I instantiateTheClass?
One way of doing so is via the spread operator (...):
functioninstantiate(TheClass,args){returnnewTheClass(...args);}
Another option is to useReflect.construct():
functioninstantiate(TheClass,args){returnReflect.construct(TheClass,args);}
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: