ES2020 / ES11 new features with examples

In this article, we’re going to review some of the latest and greatest features coming with ES2020.

ES2020 / ES11 javascript features

A set of examples of the new ES2020 javascript features :

Dynamic Import

Dynamic import() returns a promise for the module namespace object of the requested module. Thus, we can now use the import() function with the await keyword and assign the module namespace object to a variable dynamically.

// helper.js
export function fun1() {
    console.log('----fun1----');
}
export function fun2() {
    console.log('----fun2----');
}
(async function(){
    const helper = './helper.js';
    const module = await import(helper);
    module.fun1();
    module.fun2();
})();

BigInt

Now there is a native solution, BigInt is a built-in object that provides a way to represent whole numbers larger than 2⁵³ — 1, which is the largest number in JS number).

How Does It Work?

The following sections show BigInt in action. A number have been influenced by or taken outright from Mathias Bynens’s BigInt v8 update, which includes more details than this page.

Syntax

BigInt is created by appending n to the end of the integer or by calling the constructor.

const theBiggestInt = 9007199254740991n;

const alsoHuge = BigInt(9007199254740991);
// ↪ 9007199254740991n

const hugeButString = BigInt('9007199254740991');
// ↪ 9007199254740991n

Operators

You can use +*-** and % with BigInts, just like with Numbers.

const previousMaxSafe = BigInt(Number.MAX_SAFE_INTEGER);
// ↪ 9007199254740991

const maxPlusOne = previousMaxSafe + 1n;
// ↪ 9007199254740992n
 
const theFuture = previousMaxSafe + 2n;
// ↪ 9007199254740993n, this works now!

const multi = previousMaxSafe * 2n;
// ↪ 18014398509481982n

const subtr = multi – 10n;
// ↪ 18014398509481972n

const mod = multi % 10n;
// ↪ 2n

const bigN = 2n ** 54n;
// ↪ 18014398509481984n

bigN * -1n
// ↪ –18014398509481984n

Nullish Coalescing Operator

When you use || an operator, it returns the first argument to be true. However, sometimes you a default value considered as false such as 0 or "". To avoid it we can use the nullish coalescing operator ?? like below:

let object = {
    car: {
        speed: 0,
        name: ""
    }
};

console.info(object.car.speed || 90); // 90
console.info(object.car.speed ?? 90); // 0

console.info(null || true); // true
console.info(null ?? true); // true

console.info(undefined || true); // true
console.info(undefined ?? true); // true

console.info(0 || true); // true
console.info(0 ?? true); // 0

console.info("" || true); // true
console.info("" ?? true); // ""

console.info([] || true); // []
console.info([] ?? true); // []

console.info({} || true); // {}
console.info({} ?? true); // {}

console.info(true || "hey"); // true
console.info(true ?? "hey"); // true

console.info(false || true); // true
console.info(false ?? true); // false

Optional Chaining Operator

Let’s take the following object as an example:

let person = {
    name: "John",
    age: 20
};

Let’s say we want to access a property on this object that we are not sure to have, we usually do:

if (person.city !== undefined &&  person.city.locale !== undefined) {
    const cityLocale =  person.city.locale;
}

This ensures the program does not throw any “error cannot read property name of undefined”.

Now with the optional chaining operator, we can be more concise:

console.info(person?.city?.locale);

Private variable in Class

You can now declare a private variable in a class by using a hashtag #. If a private variable is called outside of its class It will throw a SyntaxError.

class MyClass {
    #privateVariable = "Hello private world"

    helloWorld() { console.info(this.#privateVariable) }
}

const myClass = new MyClass()
myClass.helloWorld() // works
console.info(myClass.#helloWorld) // SyntaxError: Private field '#helloWorld' must be declared in an enclosing class

Promise.allSettled()

Promise.allSettled takes an array of Promise object as argument and waits that all promises settle to return the corresponding result as an array of objects {status, ?value, ?reason}.

const p1 = new Promise((resolve, reject) => setTimeout(resolve, 200, {value:'promise1'}));
const p2 = new Promise((resolve, reject) => setTimeout(resolve, 400, {value:'promise2'}));
const p3 = new Promise((resolve, reject) => setTimeout(resolve, 1000, {value:'promise3'}));

Promise.allSettled([p1, p2, p3]).then((result) => {
    result.forEach(element => {
        console.log(element);
    });
});

Promise.all([p1, p2, p3]).then((result) => {
    result.forEach(element => {
        console.log(element);
    });
});

String.prototype.matchAll

String.prototype.match gives an array of all matches between a string and a regexp.

For example:

const text = "From 2019.01.29 to 2019.01.30";
const regexp = /(?<month>\d{2}).(?<day>\d{2})/gu;
const matchResults = text.match(regexp);
const matchAllResults = Array.from(text.matchAll(regexp));

console.log(matchResults);
console.log(matchAllResults);

globalThis

In HTML, the global object is separated into the Window and the WindowProxy. New attributes are set on the Window, but top-level this has the identity of the WindowProxy. The WindowProxy forwards all object operations to the underlying Window, but as the page changes, globalThis maintains the same identity while the underlying Window is swapped out.

The distinction is observable in the following scenario, with files parent.htmlframe-a.html, and frame-b.htmlframe-a.html has the following source code:

<script>
  globalThis.foo = 'a';
  globalThis.getGlobalThis = () => globalThis;
</script>

frame-b.html has the following source code:

<script>
  globalThis.getGlobalThis = () => globalThis;
</script>

parent.html’s source code is:

<iframe src="frame-a.html"></iframe>
<script>
  const iframe = document.querySelector('iframe');
  iframe.onload = () => {
    // The global variable `foo` exists.
    console.assert(frames[0].foo === 'a');
    const before = frames[0].getGlobalThis();
    iframe.onload = () => {
      // The global variable `foo` has disappeared.
      console.assert(frames[0].foo === undefined, 'The global object changes during navigation');
      const after = frames[0].getGlobalThis();
      // But, `globalThis` still has the same identity.
      console.assert(before === after, 'globalThis maintains its identity during navigation');
    };
    iframe.src = 'frame-b.html';
  };
</script>

This demo shows that the global variable foo was being stored on the actual global object, which has changed during navigation but globalThis has not changed during navigation. Therefore, globalThis is not the global object.

Thus, globalThis is observably different from “the global object”, which is not directly accessible from JavaScript. In web browsers, it’s even possible that (even in the global scope), foo !== globalThis.foo.

ES6/ES2015 does not account for the Window/WindowProxy structure, and simply refers to ”the global object” directly. This specification does the same. If the ECMAScript specification is changed for top-level this to account for WindowProxy, then the change should also apply to the definition of this proposal.

import.meta

import.meta is created by the ECMAScript implementation, with a null prototype. The host environment can return a set of properties (as key/value pairs) that will be added to the object. Finally, as an escape hatch, the object can be modified arbitrarily by the host if necessary.

The import.meta meta-property is only syntactically valid in modules, as it is meant for meta-information about the currently running module, and should not be repurposed for information about the currently-running classic script.

Example

The following code uses a couple properties that we anticipate adding to import.meta in browsers:

(async () => {
  const response = await fetch(new URL("../hamsters.jpg", import.meta.url));
  const blob = await response.blob();

  const size = import.meta.scriptElement.dataset.size || 300;

  const image = new Image();
  image.src = URL.createObjectURL(blob);
  image.width = image.height = size;

  document.body.appendChild(image);
})();

When this module is loaded, no matter its location, it will load a sibling file hamsters.jpg, and display the image. The size of the image can be configured using the script element used to import it, such as with

<script type="module" src="path/to/hamster-displayer.mjs" data-size="500"></script>

W3TWEAKS
Latest posts by W3TWEAKS (see all)

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *