Skip to content

Instantly share code, notes, and snippets.

@Chema22R
Last active August 20, 2021 14:27
Show Gist options
  • Save Chema22R/67f2ead976b602e0d094e7880a7e0d93 to your computer and use it in GitHub Desktop.
Save Chema22R/67f2ead976b602e0d094e7880a7e0d93 to your computer and use it in GitHub Desktop.
ECMAScript new features cheatsheet (post-ES6)

ECMAScript 7 (2016) [2ality]

Array.prototype.includes() [TC39][2ality][MDN]

The includes() method determines whether an array includes a certain value among its entries, returning true or false as appropriate.

const pets = ['cat', 'dog', 'bat'];

console.log(pets.includes('cat')); // expected output: true
console.log(pets.includes('at')); // expected output: false

Exponentiation Operator [TC39][2ality][MDN]

The exponentiation operator returns the result of raising the first operand to the power of the second operand. The exponentiation operator is right-associative. a ** b ** c is equal to a ** (b ** c).

2 ** 3 // 8
3 ** 2 // 9
3 ** 2.5 // 15.588457268119896
10 ** -1 // 0.1
NaN ** 2 // NaN

2 ** 3 ** 2 // 512
2 ** (3 ** 2) // 512
(2 ** 3) ** 2 // 64

JavaScript also has a bitwise operator ^ (logical XOR). ** and ^ are different (2 ** 3 === 8 when 2 ^ 3 === 1).

ECMAScript 8 (2017) [2ality]

Async Functions [TC39][2ality][MDN]

The async function declaration defines an asynchronous function that operates in a separate order than the rest of the code via the event loop, returning an implicit Promise as its result. But the syntax and structure of code using async functions looks like standard synchronous functions.

function resolveAfter2Seconds() {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve('resolved');
        }, 2000);
    });
}

async function asyncCall() {
    console.log('calling');
    const result = await resolveAfter2Seconds();
    console.log(result); // expected output (after 2s): 'resolved'
}

asyncCall();

Object.values() [TC39][2ality][MDN]

The Object.values() method returns an array of a given object's own enumerable property values, in the same order as that provided by a for...in loop (the only difference is that a for...in loop enumerates properties in the prototype chain as well).

const object1 = { a: 'hi', b: 42, c: false };
console.log(Object.values(object1)); // expected output: ["hi", 42, false]

Object.entries() [TC39][2ality][MDN]

The Object.entries() method returns an array of a given object's own enumerable string-keyed property [key, value] pairs, in the same order as that provided by a for...in loop (the only important difference is that a for...in loop enumerates properties in the prototype chain as well).

const object1 = { a: 'hi', b: 42, c: false };
console.log(Object.entries(object1)); // expected output: [["a", "hi"], ["b", 42], ["c", false]]

String.prototype.padStart() [TC39][2ality][MDN]

The padStart() method pads the current string with another string (multiple times, if needed) until the resulting string reaches the given length. The padding is applied from the start of the current string.

const str = 'hi';
console.log(str.padStart(4, '*')); // expected output: "**hi"

String.prototype.padEnd() [TC39][2ality][MDN]

The padEnd() method pads the current string with a given string (repeated, if needed) so that the resulting string reaches a given length. The padding is applied from the end of the current string.

const str = 'Hi';
console.log(str.padEnd(4, '.')); // expected output: "Hi.."

Object.getOwnPropertyDescriptors() [TC39][2ality][MDN]

The Object.getOwnPropertyDescriptors() method returns all own property descriptors of a given object.

const object1 = { property1: 42 };
const descriptors1 = Object.getOwnPropertyDescriptors(object1);
console.log(descriptors1); // expected output: { property1: { value: 42, writable: true, enumerable: true, configurable: true } }

Trailing Commas [TC39][2ality][MDN]

ECMAScript 8 (2017) allows trailing commas in function parameter lists (they cause a SyntaxError in previous versions of ECMAScript).

function f(p,) {}
(p,) => {};
f('hi',);
Math.max(10, 20,);

JavaScript has allowed trailing commas in array literals since the beginning, and later added them to object literals with ECMAScript 5. JSON, however, disallows trailing commas.

Trailing commas (sometimes called "final commas") can be useful when adding new elements, parameters, or properties to JavaScript code. If you want to add a new property, you can simply add a new line without modifying the previously last line if that line already uses a trailing comma. This makes version-control diffs cleaner and editing code might be less troublesome.

Shared Memory and Atomics [TC39][2ality][MDN]

It introduces a new constructor SharedArrayBuffer and a namespace object Atomics with helper functions:

  • The SharedArrayBuffer object is used to represent a generic, fixed-length raw binary data buffer, similar to the ArrayBuffer object, but in a way that they can be used to create views on shared memory. Unlike an ArrayBuffer, a SharedArrayBuffer cannot become detached.
  • The Atomics object provides atomic operations as static methods. They are used with SharedArrayBuffer objects.

This feature was disabled by default in all major browsers on 5 January, 2018 in response to Spectre.

ECMAScript 9 (2018) [2ality]

Asynchronous Iteration [TC39][2ality][MDN]

The for await...of statement, an asynchronous version of the for-of loop, creates a loop iterating over async iterable objects as well as on sync iterables. It invokes a custom iteration hook with statements to be executed for the value of each distinct property of the object.

async function f() {
    for await (const x of createAsyncIterable(['a', 'b'])) {
        console.log(x);
    }
}

Spread Syntax [TC39][2ality][MDN]

Spread syntax allows an iterable such as an array expression or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.

const iterableObj = [1, 2, 3];
const obj = { a: 3, c: '6' };

myFunction(...iterableObj); // for function calls
let iterableObjRes = [...iterableObj, '4', 'five', 6]; // for array literals
let objClone = { b: '2', ...obj }; // for object literals

console.log(iterableObjRes); // expected output: [1, 2, 3, "4", "five", 6]
console.log(objClone); // expected output: { b: "2", a: 3, c: "6" }

Rest Syntax [TC39][2ality][MDN]

Rest syntax is the opposite of spread syntax and is used for destructuring arrays and objects. Inside object destructuring patterns, rest property copies all enumerable own properties of the destructuring source into its operand, except those that were already mentioned in the object literal (collects the remaining own enumerable property keys that are not already picked off by the destructuring pattern).

let { a, b, ...rest1 } = { a: 1, b: 2, c: 3, d: 4 };
console.log(a); // expected output: 1
console.log(b); // expected output: 2
console.log(rest1); // expected output: { c: 3, d: 4 }

const [c, d, ...rest2] = [10, 20, 30, 40];
console.log(c); // expected output: 10
console.log(d); // expected output: 20
console.log(rest2); // expected output: [30, 40]

Promise.prototype.finally() [TC39][2ality][MDN]

The finally() method returns a Promise. When the promise is settled, i.e either fulfilled or rejected, the specified callback function is executed. This provides a way for code to be run whether the promise was fulfilled successfully or rejected once the Promise has been dealt with. This helps to avoid duplicating code in both the promise's then() and catch() handlers.

let connection;
db.open().then(conn => {
    connection = conn;
    return connection.select({ name: 'Jane' });
}).then(result => {
    // Process result
    // Use `connection` to make more queries
}).catch(error => {
    // handle errors
}).finally(() => {
    connection.close();
});

The finally() method can be useful if you want to do some processing or cleanup once the promise is settled, regardless of its outcome.

RegExp Named Capture Groups [TC39][2ality]

Named capture groups allow one to refer to certain portions of a string that a regular expression matches, identifying capture groups via names.

const regexp = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
const result = re.exec('2015-01-02');

console.log(result.groups.year); // expected output: 2015
console.log(result.groups.month); // expected output: 01
console.log(result.groups.day); // expected output: 02

RegExp Unicode Property Escapes [TC39][2ality]

Unicode property escapes allow to match characters by mentioning their Unicode character properties inside the curly braces of \p{}.

const regexp1 = /^\p{Script=Greek}+$/u;
const regexp2 = /^\p{Script=Cyrillic}+$/u;
const regexp3 = /^\p{General_Category=Currency_Symbol}+$/u;
const regexp4 = /^\P{General_Category=Currency_Symbol}+$/u;

console.log(regexp1.test('μετά')); // expected output: true
console.log(regexp2.test('Д')); // expected output: true
console.log(regexp3.test('$')); // expected output: true
console.log(regexp4.test('a')); // expected output: true

This regular expressions must have the u flag set.

\P{} is the negated form of \p{}.

RegExp Lookbehind Assertions [TC39][2ality]

Lookarounds are zero-width assertions that match a string without consuming anything. ECMAScript has lookahead assertions that does this in forward direction, but the language is missing a way to do this backward which the lookbehind assertions provide. With lookbehind assertions, one can make sure that a pattern is or isn't preceded by another. There are two versions of lookbehind assertions: positive ((?<=...)) and negative ((?<!...)).

const regexp1 = /(?<=\$)\d{2}/; // positive lookbehind
console.log(regexp1.test('$10')); // expected output: true
console.log(regexp1.test('€10')); // expected output: false

const regexp2 = /(?<!\$)\d{2}/; // negative lookbehind
console.log(regexp2.test('$10')); // expected output: false
console.log(regexp2.test('€10')); // expected output: true

RegExp s (dotAll) Flag [TC39][2ality]

In regular expression patterns, the dot . matches a single character, regardless of what character it is, except astral characters (the u (unicode) flag fixes that) and line terminator characters. The s (dotAll) flag makes . match any character.

const regex = /foo.bar/s;
console.log(regex.test('foo\nbar')); // expected output: true
console.log(regex.dotAll) // expected output: true

Lifting Template Literal Restriction [TC39][2ality]

In ES6, using tagged template literals is possible to make a function call by mentioning a function (tag function) before a template literal. Tag functions receive two versions of the fixed string pieces (template strings) in a template literal: cooked, where escape sequences are interpreted (\u{4B} becomes K), and raw, where escape sequences are normal text (\u{4B} becomes \\u{4B})

function tagFunc(tmplObj) {
    return { cooked: tmplObj, raw: tmplObj.raw };
}

console.log(tagFunc`\u{4B}`); // expected output: { cooked: [ 'K' ], raw: [ '\\u{4B}' ] }

The problem is that some text is illegal after backslashes and there is no total freedom within template literals. This prevents tagged template literals such as: tagFunc`\unicode` or tagFunc`C:\uuu\xxx\111` .

With ES9, all syntactic restrictions related to escape sequences are dropped. Then illegal escape sequences simply show up verbatim in the raw representation and every template string with an illegal escape sequence is an undefined element in the cooked Array:

function tagFunc(tmplObj) {
    return { cooked: tmplObj, raw: tmplObj.raw };
}

console.log(tagFunc`\unicode`); // expected output: { cooked: [ undefined ], raw: [ '\\unicode' ] }
console.log(tagFunc`C:\uuu\xxx\111`); // expected output: { cooked: [ undefined ], raw: [ 'C:\\uuu\\xxx\\111' ] }

ECMAScript 10 (2019) [2ality]

Array.prototype.flat() [TC39][2ality][MDN]

The flat() method creates a new array with all sub-array elements concatenated into it recursively up to the specified depth.

const arr1 = [1, 2, [3, 4]];
const arr2 = [1, 2, [3, 4, [5, 6]]];
const arr3 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
const arr4 = [1, 2, , 4, 5];

console.log(arr1.flat()); // expected output: [1, 2, 3, 4]
console.log(arr2.flat()); // expected output: [1, 2, 3, 4, [5, 6]]
console.log(arr2.flat(2)); // expected output: [1, 2, 3, 4, 5, 6]
console.log(arr3.flat(Infinity)); // expected output: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(arr4.flat()); // expected output: [1, 2, 4, 5]

Array.prototype.flatMap() [TC39][2ality][MDN]

The flatMap() method first maps each element using a mapping function, then flattens the result into a new array. It is identical to a map() followed by a flat() of depth 1, but flatMap() is often quite useful, as merging both into one method is slightly more efficient.

const arr1 = [1, 2, 3, 4];
console.log(arr1.map(x => [x * 2])); // expected output: [[2], [4], [6], [8]]
console.log(arr1.flatMap(x => [x * 2])); // expected output: [2, 4, 6, 8]

Object.fromEntries() [TC39][2ality][MDN]

The Object.fromEntries() method transforms a list of key-value pairs into an object.

const entries = [['foo', 'bar'], ['baz', 42]];
console.log(Object.fromEntries(entries)); // expected output: { foo: "bar", baz: 42 }

It does the opposite of Object.entries()

String.prototype.trimStart() [TC39][2ality][MDN]

The trimStart() method removes whitespace from the beginning of a string. trimLeft() is an alias of this method.

const greeting = '   Hello world!   ';
console.log(greeting.trimStart()); // expected output: "Hello world!   ";
console.log(greeting.trimLeft()); // expected output: "Hello world!   ";

String.prototype.trimEnd() [TC39][2ality][MDN]

The trimEnd() method removes whitespace from the end of a string. trimRight() is an alias of this method.

const greeting = '   Hello world!   ';
console.log(greeting.trimEnd()); // expected output: "   Hello world!";
console.log(greeting.trimRight()); // expected output: "   Hello world!";

Symbol.prototype.description [TC39][2ality][MDN]

The read-only description property is a string returning the optional description of Symbol objects.

console.log(Symbol('desc').description); // expected output: "desc"
console.log(Symbol.iterator.description); // expected output: "Symbol.iterator"
console.log(Symbol.for('foo').description); // expected output: "foo"
console.log(Symbol('foo').description + 'bar'); // expected output: "foobar"

Optional Catch Binding [TC39][2ality][MDN]

This feature makes the catch binding optional. This is useful whenever you don’t need the binding parameter of the catch clause.

function isValidJSON(text) {
    try {
        JSON.parse(text);
        return true;
    } catch {
        return false;
    }
}

console.log(isValidJSON('{ "msg": "aa" }')); // expected output: true
console.log(isValidJSON('aa')); // expected output: false

JSON Superset [TC39][2ality]

Before ES10, JSON (as standardized via ECMA-404) was not a subset of ECMAScript and that caused that JSON strings could contain the characters U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR while ECMAScript strings could not.

With ES10, given that the syntax of JSON is fixed, a decision was made to remove the restriction for ECMAScript string literals. That simplifies the grammar of the specification, because you don’t need separate rules for ECMAScript string literals and JSON string literals.

const sourceCode = '"\u2028"';
eval(sourceCode); // SyntaxError before ES10, OK after ES10

const json = '"\u2028"';
JSON.parse(json); // OK (before and after ES10)

Function.prototype.toString Revision [TC39][2ality]

This revision of the Function.prototype.toString brings two major improvements compared to ES7:

  • Whenever possible – source code: if a function was created via ECMAScript source code, toString() must return that source code (in ES7, whether to do so is left up to engines).
  • Otherwise – standardized placeholder: if toString() could not (or would not) create syntactically valid ECMAScript code, it must return as placeholder a function whose body is { [native code] } (in ES7, it had to return a string for which eval() throws a SyntaxError).
class C { foo() { /*hello*/ } }

console.log(C.prototype.foo.toString()); // expected output: foo() { /*hello*/ }
console.log(isNaN.toString()); // expected output: function isNaN() { [native code] }
console.log((function foo() {}).bind(null).toString()); // expected output: function () { [native code] }

Well-formed JSON.stringify [TC39][2ality]

This revision of the JSON.stringify aims to prevent from returning ill-formed Unicode strings.

RFC 8259 section 8.1 requires JSON text exchanged outside the scope of a closed ecosystem to be encoded using UTF-8, but JSON.stringify can return strings including code points that have no representation in UTF-8 (specifically, surrogate code points in the range 0xD800–0xDFFF).

However, returning such invalid Unicode strings is unnecessary, because JSON strings can include Unicode escape sequences. This revision modifies the JSON.stringify to represent unpaired surrogate code points with JSON escape sequences.

// Non-BMP characters still serialize to surrogate pairs
console.log(JSON.stringify('𝌆')); // expected output: "𝌆"
console.log(JSON.stringify('\uD834\uDF06')); // expected output: "𝌆"

// Unpaired surrogate code units will serialize to escape sequences
console.log(JSON.stringify('\uDF06\uD834')); // expected output: "\\udf06\\ud834"
console.log(JSON.stringify('\uDEAD')); // expected output: "\\udead"

ECMAScript 11 (2020) [2ality]

String.prototype.matchAll [TC39][2ality][MDN]

The matchAll() method returns an iterator of all results matching a string against a regular expression, including capturing groups.

const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';

const array = [...str.matchAll(regexp)];

console.log(array[0]); // expected output: ["test1", "e", "st1", "1"]
console.log(array[1]); // expected output: ["test2", "e", "st2", "2"]

It returns an iterator, not a true restartable iterable. That is, once the result is exhausted, you need to call the method again and create a new iterator. In contrast, .match() with the /g flag returns an iterable (an Array) over which you can iterate as often as you want.

Dynamic import() [TC39][MDN]

The standard import syntax is static and will always result in all code in the imported module being evaluated at load time. The dynamic import syntactic form allows to lazy-load a module conditionally or on demand. It returns a promise for the module namespace object of the requested module, which is created after fetching, instantiating, and evaluating all of the module's dependencies, as well as the module itself.

Some important differences compared to the usual import declaration:

  • It can be used from scripts, not just from modules.
  • If it is used in a module, it can occur anywhere at any level, and is not hoisted.
  • It accepts arbitrary and runtime-determined template strings, not just static string literals (import(`./language-packs/${navigator.language}.js`) will work; something impossible to accomplish with the usual import declarations).
  • Its presence in the module does not establish a dependency which must be fetched and evaluated before the containing module is evaluated.
  • It does not establish a dependency which can be statically analyzed.
const main = document.querySelector('main');
for (const link of document.querySelectorAll('nav > a')) {
    link.addEventListener('click', e => {
        e.preventDefault();

        import(`./modules/${link.dataset.entryModule}.js`).then(module => {
            module.loadPageInto(main);
        }).catch(err => {
            main.textContent = err.message;
        });
    });
}

Use dynamic import only when necessary. The static form is preferable for loading initial dependencies, and can benefit more readily from static analysis tools and tree shaking.

BigInt [TC39][2ality][MDN]

BigInt is a built-in object that provides a way to represent whole numbers larger than 253 - 1, which is the largest number JavaScript can reliably represent with the Number primitive and represented by the Number.MAX_SAFE_INTEGER constant. BigInt can be used for arbitrarily large integers.

A BigInt is created by appending n to the end of an integer literal or by calling the function BigInt().

const theBiggestInt = 9007199254740991n; // 9007199254740991n
const alsoHuge = BigInt(9007199254740991); // 9007199254740991n
const hugeString = BigInt('9007199254740991'); // 9007199254740991n
const hugeHex = BigInt('0x1fffffffffffff'); // 9007199254740991n
const hugeBin = BigInt('0b11111111111111111111111111111111111111111111111111111'); // 9007199254740991n

console.log(
    theBiggestInt === alsoHuge &&
    alsoHuge === hugeString &&
    hugeString === hugeHex &&
    hugeHex === hugeBin
); // expected output: true

BigInt is similar to Number in some ways, but also differs in a few key matters: it cannot be used with methods in the built-in Math object and cannot be mixed with instances of Number in operations; they must be coerced to the same type. Be careful coercing values back and forth, however, as the precision of a BigInt may be lost when it is coerced to a Number.

Promise.allSettled [TC39][2ality][MDN]

The Promise.allSettled() method returns a promise that resolves after all of the given promises have either resolved or rejected, with an array of objects that each describes the outcome of each promise.

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 2000));
const promise3 = new Promise((resolve, reject) => setTimeout(resolve, 1000));

const promises = [promise1, promise2, promise3];

Promise.allSettled(promises).then(results => {
    results.forEach(result => console.log(result.status)); // expected output: "fulfilled"\n"rejected"\n"fulfilled"
});

globalThis [TC39][MDN]

The global globalThis property contains the global this value, which is akin to the global object.

Historically, accessing the global object has required different syntax in different JavaScript environments. On the web, it is accessible as window, self or frames; on node.js, it is global; on web workers, it is self.

The globalThis property provides a standard way of accessing the global this value (and hence the global object itself) across environments.

function canMakeHTTPRequest() {
    return typeof globalThis.XMLHttpRequest === 'function';
}

console.log(canMakeHTTPRequest()); // expected output (in a browser): true

globalThis property is writable and configurable, but not enumerable.

for-in mechanics [TC39]

ECMA-262 leaves the order of for (a in b) almost totally unspecified, but real engines tend to be consistent in at least some cases.

Historical efforts to get consensus on a complete specification of the order of for-in have repeatedly failed. This in part because all engines have their own idiosyncratic implementations that are the result of a great deal of work and that they don’t really want to revisit.

In conclusion, the different engines have agreed on how properties are iterated when using the for (a in b) control structure so that the behavior is standardized.

Optional Chaining [TC39][2ality][MDN]

The optional chaining operator ?. permits reading the value of a property located deep within a chain of connected objects without having to expressly validate that each reference in the chain is valid. The ?. operator functions similarly to the . chaining operator, except that instead of causing an error if a reference is nullish (null or undefined), the expression short-circuits with a return value of undefined. When used with function calls, it returns undefined if the given function does not exist.

const obj = { cat: { name: 'Yami' } };

console.log(obj.dog?.name); // expected output: undefined
console.log(obj.nonExistentFunction?.('arg1', 'arg2')); // expected output: undefined

Nullish coalescing Operator [TC39][2ality][MDN]

The nullish coalescing operator ?? is a logical operator that returns its right-hand side operand when its left-hand side operand is null or undefined, and otherwise returns its left-hand side operand.

console.log(0 ?? 42); // expected output: 0
console.log(0 ?? undefined); // expected output: 0
console.log(0 ?? null); // expected output: 0
console.log(undefined ?? 42); // expected output: 42
console.log(null ?? 42); // expected output: 42
console.log(null ?? undefined); // expected output: undefined

Contrary to the logical OR || operator, the left operand is returned if it is a falsy value which is not null or undefined. In other words, if you use || to provide some default value to another variable, you may encounter unexpected behaviors if you consider some falsy values as usable (eg. '' or 0).

import.meta [TC39][MDN]

The import.meta object exposes context-specific metadata to a JavaScript module. It contains information about the module (like the module's URL).

<script type="module" src="my-module.js"></script>
console.log(import.meta); // expected output: { url: "file:///home/user/my-module.js" }

The import.meta object is created by the ECMAScript implementation, with a null prototype. The object is extensible, and its properties are writable, configurable, and enumerable.

ECMAScript 12 (2021) [2ality]

String.prototype.replaceAll [TC39][2ality][MDN]

Waiting for this feature to achieve cross-browser stability.

Promise.any() [TC39][2ality][MDN]

Waiting for this feature to achieve cross-browser stability.

WeakRefs [TC39][2ality][MDN]

Waiting for this feature to achieve cross-browser stability.

Logical assignment operators [TC39][2ality][MDN]

Waiting for this feature to achieve cross-browser stability.

Numeric Separators [TC39][2ality][MDN]

Waiting for this feature to achieve cross-browser stability.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment