This chapter lists what’s new in recent ECMAScript versions – in reverse chronological order. It ends before ES6 (ES2015): ES2016 was the first truly incremental release of ECMAScript – which is why ES6 has too many features to list here. If you want to get a feeling for earlier releases:
Grouping synchronous iterables:
Map.groupBy()
groups the items of an iterable into Map entries whose keys are provided by a callback:
assert.deepEqual(
Map.groupBy([0, -5, 3, -4, 8, 9], x => Math.sign(x)),
new Map()
.set(0, [0])
.set(-1, [-5,-4])
.set(1, [3,8,9])
);
There is also Object.groupBy()
which produces an object instead of a Map:
assert.deepEqual(
Object.groupBy([0, -5, 3, -4, 8, 9], x => Math.sign(x)),
{
'0': [0],
'-1': [-5,-4],
'1': [3,8,9],
__proto__: null,
}
);
Promise.withResolvers()
provides a new way of creating Promises that we want to resolve:
const { promise, resolve, reject } = Promise.withResolvers();
The new regular expression flag /v
(.unicodeSets
) enables these features:
Escapes for Unicode string properties (😵💫 consists of three code points):
// Previously: Unicode code point property `Emoji` via /u
assert.equal(
/^\p{Emoji}$/u.test('😵💫'), false
);
// New: Unicode string property `RGI_Emoji` via /v
assert.equal(
/^\p{RGI_Emoji}$/v.test('😵💫'), true
);
String literals via \q{}
in character classes:
> /^[\q{😵💫}]$/v.test('😵💫')
true
> /^[\q{abc|def}]$/v.test('abc')
true
Set operations for character classes:
> /^[\w--[a-g]]$/v.test('a')
false
> /^[\p{Number}--[0-9]]$/v.test('٣')
true
> /^[\p{RGI_Emoji}--\q{😵💫}]$/v.test('😵💫')
false
Improved matching with /i
if a Unicode property escape is negated via [^···]
ArrayBuffers get two new features:
They can be resized in place:
const buf = new ArrayBuffer(2, {maxByteLength: 4});
// `typedArray` starts at offset 2
const typedArray = new Uint8Array(buf, 2);
assert.equal(
typedArray.length, 0
);
buf.resize(4);
assert.equal(
typedArray.length, 2
);
They get a method .transfer()
for transferring them.
SharedArrayBuffers can be resized, but they can only grow and never shrink. They are not transferrable and therefore don’t get the method .transfer()
that ArrayBuffers
got.
Two new methods help us ensure that strings are well-formed (w.r.t. UTF-16 code units):
.isWellFormed()
checks if a JavaScript string is well-formed and does not contain any lone surrogates.
.toWellFormed()
returns a copy of the receiver where each lone surrogate is replaced with the code unit 0xFFFD (which represents the code point with the same number, whose name is “replacement character”). The result is therefore well-formed.
Atomics.waitAsync()
lets us wait asynchronously for a change to shared memory. Its functionality is beyond the scope of this book. See the MDN Web Docs for more information.
“Change Array by copy”: Arrays and Typed Arrays get new non-destructive methods that copy receivers before changing them:
.toReversed()
is the non-destructive version of .reverse()
:
const original = ['a', 'b', 'c'];
const reversed = original.toReversed();
assert.deepEqual(reversed, ['c', 'b', 'a']);
// The original is unchanged
assert.deepEqual(original, ['a', 'b', 'c']);
.toSorted()
is the non-destructive version of .sort()
:
const original = ['c', 'a', 'b'];
const sorted = original.toSorted();
assert.deepEqual(sorted, ['a', 'b', 'c']);
// The original is unchanged
assert.deepEqual(original, ['c', 'a', 'b']);
.toSpliced()
is the non-destructive version of .splice()
:
const original = ['a', 'b', 'c', 'd'];
const spliced = original.toSpliced(1, 2, 'x');
assert.deepEqual(spliced, ['a', 'x', 'd']);
// The original is unchanged
assert.deepEqual(original, ['a', 'b', 'c', 'd']);
.with()
is the non-destructive version of setting a value with square brackets:
const original = ['a', 'b', 'c'];
const updated = original.with(1, 'x');
assert.deepEqual(updated, ['a', 'x', 'c']);
// The original is unchanged
assert.deepEqual(original, ['a', 'b', 'c']);
“Array find from last”: Arrays and Typed Arrays get two new methods:
.findLast()
is similar to .find()
but starts searching at the end of an Array:
> ['', 'a', 'b', ''].findLast(s => s.length > 0)
'b'
.findLastIndex()
is similar to .findIndex()
but starts searching at the end of an Array:
> ['', 'a', 'b', ''].findLastIndex(s => s.length > 0)
2
Symbols as WeakMap keys: Before this feature, only objects could be used as keys in WeakMaps. This feature also lets us use symbols – except for registered symbols (created via Symbol.for()
).
“Hashbang grammar”: JavaScript now ignores the first line of a file if it starts with a hash (#
) and a bang (!
). Some JavaScript runtimes, such as Node.js, have done this for a long time. Now it is also part of the language proper. This is an example of a “hashbang” line:
#!/usr/bin/env node
New members of classes:
Private slot checks (“ergonomic brand checks for private fields”): The following expression checks if obj
has a private slot #privateSlot
:
#privateSlot in obj
Top-level await
in modules: We can now use await
at the top levels of modules and don’t have to enter async functions or methods anymore.
error.cause
: Error
and its subclasses now let us specify which error caused the current one:
new Error('Something went wrong', {cause: otherError})
Method .at()
of indexable values lets us read an element at a given index (like the bracket operator []
) and supports negative indices (unlike the bracket operator).
> ['a', 'b', 'c'].at(0)
'a'
> ['a', 'b', 'c'].at(-1)
'c'
The following “indexable” types have method .at()
:
string
Array
Uint8Array
etc.
RegExp match indices: If we add a flag to a regular expression, using it produces match objects that record the start and end index of each group capture.
Object.hasOwn(obj, propKey)
provides a safe way to check if an object obj
has an own property with the key propKey
.
String.prototype.replaceAll()
lets us replace all matches of a regular expression or a string (.replace()
only replaces the first occurrence of a string):
> 'abbbaab'.replaceAll('b', 'x')
'axxxaax'
Promise.any()
and AggregateError
: Promise.any()
returns a Promise that is fulfilled as soon as the first Promise in an iterable of Promises is fulfilled. If there are only rejections, they are put into an AggregateError
which becomes the rejection value.
We use Promise.any()
when we are only interested in the first fulfilled Promise among several.
a ||= b
a &&= b
a ??= b
Underscores (_
) as separators in:
123_456.789_012
6_000_000_000_000_000_000_000_000n
WeakRefs: This feature is beyond the scope of this book. Quoting its proposal states:
WeakRef
class
FinalizationRegistry
class
Array.prototype.sort
has been stable since ES2019. In ES2021, “[it] was made more precise, reducing the amount of cases that result in an implementation-defined sort order” [source]. For more information, see the pull request for this improvement.
New module features:
Dynamic imports via import()
: The normal import
statement is static: We can only use it at the top levels of modules and its module specifier is a fixed string. import()
changes that. It can be used anywhere (including conditional statements) and we can compute its argument.
import.meta
contains metadata for the current module. Its first widely supported property is import.meta.url
which contains a string with the URL of the current module’s file.
Namespace re-exporting: The following expression imports all exports of module 'mod'
in a namespace object ns
and exports that object.
export * as ns from 'mod';
Optional chaining for property accesses and method calls. One example of optional chaining is:
value?.prop
This expression evaluates to undefined
if value
is either undefined
or null
. Otherwise, it evaluates to value.prop
. This feature is especially useful in chains of property reads when some of the properties may be missing.
Nullish coalescing operator (??
):
value ?? defaultValue
This expression is defaultValue
if value
is either undefined
or null
and value
otherwise. This operator lets us use a default value whenever something is missing.
Previously the Logical Or operator (||
) was used in this case but it has downsides here because it returns the default value whenever the left-hand side is falsy (which isn’t always correct).
Bigints – arbitrary-precision integers: Bigints are a new primitive type. It supports integer numbers that can be arbitrarily large (storage for them grows as necessary).
String.prototype.matchAll()
: This method throws if flag /g
isn’t set and returns an iterable with all match objects for a given string.
Promise.allSettled()
receives an iterable of Promises. It returns a Promise that is fulfilled once all the input Promises are settled. The fulfillment value is an Array with one object per input Promise – either one of:
{ status: 'fulfilled', value: «fulfillment value» }
{ status: 'rejected', reason: «rejection value» }
globalThis
provides a way to access the global object that works both on browsers and server-side platforms such as Node.js and Deno.
for-in
mechanics: This feature is beyond the scope of this book. For more information on it, see its proposal.
export * as ns from './internal.mjs';
Array method .flatMap()
works like .map()
but lets the callback return Arrays of zero or more values instead of single values. The returned Arrays are then concatenated and become the result of .flatMap()
. Use cases include:
Array method .flat()
converts nested Arrays into flat Arrays. Optionally, we can tell it at which depth of nesting it should stop flattening.
Object.fromEntries()
creates an object from an iterable over entries. Each entry is a two-element Array with a property key and a property value.
String methods: .trimStart()
and .trimEnd()
work like .trim()
but remove whitespace only at the start or only at the end of a string.
Optional catch
binding: We can now omit the parameter of a catch
clause if we don’t use it.
Symbol.prototype.description
is a getter for reading the description of a symbol. Previously, the description was included in the result of .toString()
but couldn’t be accessed individually.
.sort()
for Arrays and Typed Arrays is now guaranteed to be stable: If elements are considered equal by sorting, then sorting does not change the order of those elements (relative to each other).
These ES2019 features are beyond the scope of this book:
JSON.stringify()
: See 2ality blog post.
Function.prototype.toString()
revision: See 2ality blog post.
Asynchronous iteration is the asynchronous version of synchronous iteration. It is based on Promises:
await
before we can access an item.
for-of
loops. With asynchronous iterables, we use for-await-of
loops.
Spreading into object literals: By using spreading (...
) inside an object literal, we can copy the properties of another object into the current one. One use case is to create a shallow copy of an object obj
:
const shallowCopy = {...obj};
Rest properties (destructuring): When object-destructuring a value, we can now use rest syntax (...
) to get all previously unmentioned properties in an object.
const {a, ...remaining} = {a: 1, b: 2, c: 3};
assert.deepEqual(remaining, {b: 2, c: 3});
Promise.prototype.finally()
is related to the finally
clause of a try-catch-finally statement – similarly to how the Promise method .then()
is related to the try
clause and .catch()
is related to the catch
clause.
On other words: The callback of .finally()
is executed regardless of whether a Promise is fulfilled or rejected.
New Regular expression features:
RegExp
named capture groups: In addition to accessing groups by number, we can now name them and access them by name:
const matchObj = '---756---'.match(/(?<digits>[0-9]+)/)
assert.equal(matchObj.groups.digits, '756');
RegExp
lookbehind assertions complement lookahead assertions:
(?<=X)
matches if the current location is preceded by 'X'
.
(?<!X)
matches if the current location is not preceded by '(?<!X)'
.
s
(dotAll
) flag for regular expressions. If this flag is active, the dot matches line terminators (by default, it doesn’t).
RegExp
Unicode property escapes give us more power when matching sets of Unicode code points – for example:
> /^\p{Lowercase_Letter}+$/u.test('aüπ')
true
> /^\p{White_Space}+$/u.test('\n \t')
true
> /^\p{Script=Greek}+$/u.test('ΩΔΨ')
true
Template literal revision allows text with backslashes in tagged templates that is illegal in string literals – for example:
windowsPath`C:\uuu\xxx\111`
latex`\unicode`
Async functions (async/await
) let us use synchronous-looking syntax to write asynchronous code.
Object.values()
returns an Array with the values of all enumerable string-keyed properties of a given object.
Object.entries()
returns an Array with the key-value pairs of all enumerable string-keyed properties of a given object. Each pair is encoded as a two-element Array.
String padding: The string methods .padStart()
and .padEnd()
insert padding text until the receivers are long enough:
> '7'.padStart(3, '0')
'007'
> 'yes'.padEnd(6, '!')
'yes!!!'
Trailing commas in function parameter lists and calls: Trailing commas have been allowed in Arrays literals since ES3 and in Object literals since ES5. They are now also allowed in function calls and method calls.
Object.getOwnPropertyDescriptors()
lets us define properties via an object with property descriptors:
The feature “Shared memory and atomics” is beyond the scope of this book. For more information on it, see:
SharedArrayBuffer
and Atomics
on MDN Web Docs
Array.prototype.includes()
checks if an Array contains a given value.
> 4 ** 2
16
ECMAScript feature lists were taken from the TC39 page on finished proposals.