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: await import(path)
- BigInt()
- Nullish Coalescing Operator (??)
- Optional Chaining Operator (?.)
- Private Variable in Class
- Promise.AllSettled
- String.prototype.matchAll
- globalThis
- import.meta
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.
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
A 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 BigInt
s, just like with Number
s.
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.html
, frame-a.html
, and frame-b.html
. frame-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>
Leave a Reply