This chapter collects the overview sections of all the chapters in this book.
Math
features
Number
propertiesMath
methodslet
const
...
)Object
for-of
loop
then()
callsThe introduction of the ES6 specification lists all new features:
Some of [ECMAScript 6’s] major enhancements include modules, class declarations, lexical block scoping, iterators and generators, promises for asynchronous programming, destructuring patterns, and proper tail calls. The ECMAScript library of built-ins has been expanded to support additional data abstractions including maps, sets, and arrays of binary numeric values as well as additional support for Unicode supplemental characters in strings and regular expressions. The built-ins are now extensible via subclassing.
There are three major categories of features:
Math
features You can now specify integers in binary and octal notation:
Number
properties The global object Number
gained a few new properties:
Number.EPSILON
for comparing floating point numbers with a tolerance for rounding errors.Number.isInteger(num)
checks whether num
is an integer (a number without a decimal fraction):
Number.isSafeInteger(number)
Number.MIN_SAFE_INTEGER
Number.MAX_SAFE_INTEGER
Number.isNaN(num)
checks whether num
is the value NaN
. In contrast to the global function isNaN()
, it doesn’t coerce its argument to a number and is therefore safer for non-numbers:
Number
are mostly equivalent to the global functions with the same names: Number.isFinite
, Number.parseFloat
, Number.parseInt
.Math
methods The global object Math
has new methods for numerical, trigonometric and bitwise operations. Let’s look at four examples.
Math.sign()
returns the sign of a number:
Math.trunc()
removes the decimal fraction of a number:
Math.log10()
computes the logarithm to base 10:
Math.hypot()
Computes the square root of the sum of the squares of its arguments (Pythagoras’ theorem):
New string methods:
ES6 has a new kind of string literal, the template literal:
Symbols are a new primitive type in ECMAScript 6. They are created via a factory function:
Every time you call the factory function, a new and unique symbol is created. The optional parameter is a descriptive string that is shown when printing the symbol (it has no other purpose):
Symbols are mainly used as unique property keys – a symbol never clashes with any other property key (symbol or string). For example, you can make an object iterable (usable via the for-of
loop and other language mechanisms), by using the symbol stored in Symbol.iterator
as the key of a method (more information on iterables is given in the chapter on iteration):
In line A, a symbol is used as the key of the method. This unique marker makes the object iterable and enables us to use the for-of
loop.
In ECMAScript 5, you may have used strings to represent concepts such as colors. In ES6, you can use symbols and be sure that they are always unique:
Every time you call Symbol('Red')
, a new symbol is created. Therefore, COLOR_RED
can never be mistaken for another value. That would be different if it were the string 'Red'
.
Coercing (implicitly converting) symbols to strings throws exceptions:
The only solution is to convert explicitly:
Forbidding coercion prevents some errors, but also makes working with symbols more complicated.
The following operations are aware of symbols as property keys:
Reflect.ownKeys()
[]
Object.assign()
The following operations ignore symbols as property keys:
Object.keys()
Object.getOwnPropertyNames()
for-in
loopES6 has two new kinds of literals: template literals and tagged template literals. These two literals have similar names and look similar, but they are quite different. It is therefore important to distinguish:
Template literals are string literals that can stretch across multiple lines and include interpolated expressions (inserted via ${···}
):
Tagged template literals (short: tagged templates) are created by mentioning a function before a template literal:
Tagged templates are function calls. In the previous example, the method String.raw
is called to produce the result of the tagged template.
ES6 provides two new ways of declaring variables: let
and const
, which mostly replace the ES5 way of declaring variables, var
.
let
let
works similarly to var
, but the variable it declares is block-scoped, it only exists within the current block. var
is function-scoped.
In the following code, you can see that the let
-declared variable tmp
only exists inside the block that starts in line A:
const
const
works like let
, but the variable you declare must be immediately initialized, with a value that can’t be changed afterwards.
Since for-of
creates one binding (storage space for a variable) per loop iteration, it is OK to const
-declare the loop variable:
The following table gives an overview of six ways in which variables can be declared in ES6 (inspired by a table by kangax):
Hoisting | Scope | Creates global properties | |
---|---|---|---|
var |
Declaration | Function | Yes |
let |
Temporal dead zone | Block | No |
const |
Temporal dead zone | Block | No |
function |
Complete | Block | Yes |
class |
No | Block | No |
import |
Complete | Module-global | No |
Destructuring is a convenient way of extracting multiple values from data stored in (possibly nested) objects and Arrays. It can be used in locations that receive data (such as the left-hand side of an assignment). How to extract the values is specified via patterns (read on for examples).
Destructuring objects:
Destructuring helps with processing return values:
Array destructuring (works for all iterable values):
Destructuring helps with processing return values:
Destructuring can be used in the following locations (I’m showing Array patterns to demonstrate; object patterns work just as well):
You can also destructure in a for-of
loop:
Parameter handling has been significantly upgraded in ECMAScript 6. It now supports parameter default values, rest parameters (varargs) and destructuring.
Additionally, the spread operator helps with function/method/constructor calls and Array literals.
A default parameter value is specified for a parameter via an equals sign (=
). If a caller doesn’t provide a value for the parameter, the default value is used. In the following example, the default parameter value of y
is 0:
If you prefix a parameter name with the rest operator (...
), that parameter receives all remaining parameters via an Array:
You can simulate named parameters if you destructure with an object pattern in the parameter list:
The = {}
in line A enables you to call selectEntries()
without paramters.
...
) In function and constructor calls, the spread operator turns iterable values into arguments:
In Array literals, the spread operator turns iterable values into Array elements:
In ES5, a single construct, the (traditional) function, played three roles:
In ES6, there is more specialization. The three duties are now handled as follows. As far as function definitions and class definitions are concerned, a definition is either a declaration or an expression.
Especially for callbacks, arrow functions are handy, because they don’t shadow the this
of the surrounding scope.
For longer callbacks and stand-alone functions, traditional functions can be OK. Some APIs use this
as an implicit parameter. In that case, you have no choice but to use traditional functions.
Note that I distinguish:
Even though their behaviors differ (as explained later), all of these entities are functions. For example:
There are two benefits to arrow functions.
First, they are less verbose than traditional function expressions:
Second, their this
is picked up from surroundings (lexical). Therefore, you don’t need bind()
or that = this
, anymore.
The following variables are all lexical inside arrow functions:
arguments
super
this
new.target
Method definitions:
Property value shorthands:
Computed property keys:
This new syntax can also be used for method definitions:
The main use case for computed property keys is to make it easy to use symbols as property keys.
Object
The most important new method of Object
is assign()
. Traditionally, this functionality was called extend()
in the JavaScript world. In contrast to how this classic operation works, Object.assign()
only considers own (non-inherited) properties.
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 typeof
:
JavaScript has had modules for a long time. However, they were implemented via libraries, not built into the language. ES6 is the first time that JavaScript has built-in modules.
ES6 modules are stored in files. There is exactly one module per file and one file per module. You have two ways of exporting things from a module. These two ways can be mixed, but it is usually better to use them separately.
There can be multiple named exports:
You can also import the complete module:
There can be a single default export. For example, a function:
Or a class:
Note that there is no semicolon at the end if you default-export a function or a class (which are anonymous declarations).
Scripts | Modules | |
---|---|---|
HTML element | <script> |
<script type="module"> |
Default mode | non-strict | strict |
Top-level variables are | global | local to module |
Value of this at top level |
window |
undefined |
Executed | synchronously | asynchronously |
Declarative imports (import statement) |
no | yes |
Programmatic imports (Promise-based API) | yes | yes |
File extension | .js |
.js |
for-of
loop for-of
is a new loop in ES6 that replaces both for-in
and forEach()
and supports the new iteration protocol.
Use it to loop over iterable objects (Arrays, strings, Maps, Sets, etc.; see Chap. “Iterables and iterators”):
break
and continue
work inside for-of
loops:
Access both elements and their indices while looping over an Array (the square brackets before of
mean that we are using destructuring):
Looping over the [key, value] entries in a Map (the square brackets before of
mean that we are using destructuring):
New static Array
methods:
Array.from(arrayLike, mapFunc?, thisArg?)
Array.of(...items)
New Array.prototype
methods:
Array.prototype.entries()
Array.prototype.keys()
Array.prototype.values()
Array.prototype.find(predicate, thisArg?)
Array.prototype.findIndex(predicate, thisArg?)
Array.prototype.copyWithin(target, start, end=this.length)
Array.prototype.fill(value, start=0, end=this.length)
Among others, the following four data structures are new in ECMAScript 6: Map
, WeakMap
, Set
and WeakSet
.
The keys of a Map can be arbitrary values:
You can use an Array (or any iterable) with [key, value] pairs to set up the initial data in the Map:
A Set is a collection of unique elements:
As you can see, you can initialize a Set with elements if you hand the constructor an iterable (arr
in the example) over those elements.
A WeakMap is a Map that doesn’t prevent its keys from being garbage-collected. That means that you can associate data with objects without having to worry about memory leaks. For example:
Typed Arrays are an ECMAScript 6 API for handling binary data.
Code example:
Instances of ArrayBuffer
store the binary data to be processed. Two kinds of views are used to access the data:
Uint8Array
, Int16Array
, Float32Array
, etc.) interpret the ArrayBuffer as an indexed sequence of elements of a single type.DataView
let you access data as elements of several types (Uint8
, Int16
, Float32
, etc.), at any byte offset inside an ArrayBuffer.The following browser APIs support Typed Arrays (details are mentioned in a dedicated section):
ES6 introduces a new mechanism for traversing data: iteration. Two concepts are central to iteration:
Symbol.iterator
. That method is a factory for iterators.Expressed as interfaces in TypeScript notation, these roles look like this:
The following values are iterable:
Plain objects are not iterable (why is explained in a dedicated section).
Language constructs that access data via iteration:
for-of
loop:
Array.from()
:
...
):
Promise.all()
, Promise.race()
:
yield*
:
You can think of generators as processes (pieces of code) that you can pause and resume:
Note the new syntax: function*
is a new “keyword” for generator functions (there are also generator methods). yield
is an operator with which a generator can pause itself. Additionally, generators can also receive input and send output via yield
.
When you call a generator function genFunc()
, you get a generator object genObj
that you can use to control the process:
The process is initially paused in line A. genObj.next()
resumes execution, a yield
inside genFunc()
pauses execution:
There are four kinds of generators:
The objects returned by generators are iterable; each yield
contributes to the sequence of iterated values. Therefore, you can use generators to implement iterables, which can be consumed by various ES6 language mechanisms: for-of
loop, spread operator (...
), etc.
The following function returns an iterable over the properties of an object, one [key, value] pair per property:
objectEntries()
is used like this:
How exactly objectEntries()
works is explained in a dedicated section. Implementing the same functionality without generators is much more work.
You can use generators to tremendously simplify working with Promises. Let’s look at a Promise-based function fetchJson()
and how it can be improved via generators.
With the library co and a generator, this asynchronous code looks synchronous:
ECMAScript 2017 will have async functions which are internally based on generators. With them, the code looks like this:
All versions can be invoked like this:
Generators can receive input from next()
via yield
. That means that you can wake up a generator whenever new data arrives asynchronously and to the generator it feels like it receives the data synchronously.
The following regular expression features are new in ECMAScript 6:
/y
(sticky) anchors each match of a regular expression to the end of the previous match./u
(unicode) handles surrogate pairs (such as \uD83D\uDE80
) as code points and lets you use Unicode code point escapes (such as \u{1F680}
) in regular expressions.flags
gives you access to the flags of a regular expression, just like source
already gives you access to the pattern in ES5:
RegExp()
to make a copy of a regular expression:
Promises are an alternative to callbacks for delivering the results of an asynchronous computation. They require more effort from implementors of asynchronous functions, but provide several benefits for users of those functions.
The following function returns a result asynchronously, via a Promise:
You call asyncFunc()
as follows:
then()
calls then()
always returns a Promise, which enables you to chain method calls:
How the Promise P returned by then()
is settled depends on what its callback does:
asyncFunction2
’s Promise.Furthermore, note how catch()
handles the errors of two asynchronous function calls (asyncFunction1()
and asyncFunction2()
). That is, uncaught errors are passed on until there is an error handler.
If you chain asynchronous function calls via then()
, they are executed sequentially, one at a time:
If you don’t do that and call all of them immediately, they are basically executed in parallel (a fork in Unix process terminology):
Promise.all()
enables you to be notified once all results are in (a join in Unix process terminology). Its input is an Array of Promises, its output a single Promise that is fulfilled with an Array of the results.
The Promise API is about delivering results asynchronously. A Promise object (short: Promise) is a stand-in for the result, which is delivered via that object.
States:
Reacting to state changes:
then()
, to be notified of a fulfillment or a rejection.then()
method. Whenever the API is only interested in being notified of settlements, it only demands thenables (e.g. the values returned from then()
and catch()
; or the values handed to Promise.all()
and Promise.race()
).Changing states: There are two operations for changing the state of a Promise. After you have invoked either one of them once, further invocations have no effect.
Proxies enable you to intercept and customize operations performed on objects (such as getting properties). They are a metaprogramming feature.
In the following example, proxy
is the object whose operations we are intercepting and handler
is the object that handles the interceptions. In this case, we are only intercepting a single operation, get
(getting properties).
When we get the property proxy.foo
, the handler intercepts that operation:
Consult the reference for the complete API for a list of operations that can be intercepted.