ES2020 new features
Foreword
ES2020 is the 2020 version of ECMAScript. This release doesn't contain as many new features as ES6 (ES2015). However, many interesting and useful features have also been added.
This article introduces the new features of ES2020 with simple code examples. This way, you can quickly understand these new features without complicated explanations.
Optional Chaining
Optional chaining eliminates the need for redundant pre-checks when querying objects with multiple levels.
In day-to-day development, when you need to access properties nested several layers inside an object, you can get the notorious error Uncaught TypeError: Cannot read property...
that stops the entire program from running.
So, you need to modify your code to handle every possible undefined object in the property chain, for example:
let nestedProp = obj && obj.first && obj.first.second;
Before accessing obj.first.second, make sure that the values of obj and obj.first are not null (and not undefined).
With optional chained calls, similar tedious pre-validation operations can be greatly simplified and more secure:
let nestedProp = obj?.first?.second;
If obj or obj.first is null/undefined, the expression will short-circuit and return undefined.
Nullish coalescing Operator
When we query an attribute, we often set a default value without the attribute, such as the following two ways:
let c = a ? a : b // way 1
let c = a || b // way 2
Both of these methods have the obvious drawback that they will overwrite all false values like (0, '', false) which might be valid inputs in some cases.
let x = {
profile: {
name: 'boating in the waves',
age: ''
}
}
console.log(x.profile.age || 18) //18
In the above example, the attribute of age is an empty string, but it is regarded as a false value. In order to solve this problem, ES2020 has a new feature - the empty space merge operator, which is represented by ??. If the expression on the left-hand side of ?? evaluates to undefined or null , the default value on its right-hand side is returned.
let c = a ?? b;
// Equivalent to let c = a !== undefined && a !== null ? a : b;
For example there is the following code:
const x = null;
const y = x ?? 500;
console.log(y); // 500
const n = 0
const m = n ?? 9000;
console.log(m) // 0
Promise.allSettled
We know that Promise.all has the ability to execute asynchronous tasks concurrently. But its biggest problem is that if any of the promises in the parameters is rejected, the entire Promise.all call will terminate immediately and return a new Promise object that rejects.
const promises = [
Promise.resolve(1),
Promise.resolve(2),
Promise.reject('error')
];
Promise.all(promises)
.then(responses => console.log(responses))
.catch(e => console.log(e)) // "error"
If there is such a scenario: a page has three areas, corresponding to three independent interface data, use Promise.all to concurrently request three interfaces, if any one of the interfaces is abnormal, the status is reject, which will cause the page All the data in the three areas cannot be obtained. We cannot accept this situation. The appearance of Promise.allSettled can solve this pain point:
Promise.allSettled([
Promise.reject({ code: 500, msg: 'Service exception' }),
Promise.resolve({ code: 200, list: [] }),
Promise.resolve({ code: 200, list: [] })
]).then(res => {
console.log(res)
/*
0: {status: "rejected", reason: {…}}
1: {status: "fulfilled", value: {…}}
2: {status: "fulfilled", value: {…}}
*/
// Filter out the rejected state and ensure as much data rendering in the page area as possible
RenderContent(
res.filter(el => {
return el.status !== 'rejected'
})
)
})
Promise.allSettled is similar to Promise.all. Its parameter accepts an array of Promises and returns a new Promise. The only difference is that it does not short-circuit , that is to say, when all Promises are processed, we can get each Promise. The state of the Promise, regardless of whether it was successfully processed or not.
String.prototype.matchAll
If a regular expression has multiple matches in the string, now generally use the g modifier or y modifier, and take them out one by one in the loop.
function collectGroup1 (regExp, str) {
const matches = []
while (true) {
const match = regExp.exec(str)
if (match === null) break
matches.push(match[1])
}
return matches
}
console.log(collectGroup1(/"([^"]*)"/g, `"foo" and "bar" and "baz"`))
// [ 'foo', 'bar', 'baz' ]
It's worth noting that without the modifier /g, .exec() only returns the first match. Now with the String.prototype.matchAll method, all matches can be retrieved at once.
function collectGroup1 (regExp, str) {
let results = []
for (const match of str.matchAll(regExp)) {
results.push(match[1])
}
return results
}
console.log(collectGroup1(/"([^"]*)"/g, `"foo" and "bar" and "baz"`))
// ["foo", "bar", "baz"]
In the above code, since string.matchAll(regex) returns a traverser, it can be retrieved with a for...of loop.
Dynamic import
Now that the front-end packaging resources are getting bigger and bigger, there is no need to load all these logical resources when the front-end application is initialized. In order to render the first screen faster, many times, modules are dynamically imported (loaded on demand), such as lazy loading of pictures, etc. Can help you improve the performance of your application.
The on-demand loading of these logical resources is generally executed in an event callback:
el.onclick = () => {
import('/modules/my-module.js')
.then(module => {
// Do something with the module.
})
.catch(err => {
// load error;
})
}
import() can be used in script scripts, and the import(module) function can be called anywhere. It returns a promise that resolves to a module object.
This usage also supports the await keyword.
let module = await import('/modules/my-module.js');
By importing code dynamically, you can reduce the time it takes for your application to load and get something back to the user as quickly as possible.
BigInt
One of the reasons javascript has always been terrible at Math is that it can only safely represent values up -(2^53-1)to the 2^53-1range , i.e. up Number.MIN_SAFE_INTEGERto Number.MAX_SAFE_INTEGER, integer computations or representations outside this range lose precision.
var num = Number.MAX_SAFE_INTEGER; // -> 9007199254740991
num = num + 1; // -> 9007199254740992
// After adding +1 again, it will not work properly
num = num + 1; // -> 9007199254740992
// Two different values, but return true
9007199254740992 === 9007199254740993 // -> true
So BigInt came into being, it is the 7th primitive type, which can safely perform large integer calculations .
You can use the same operators on BigInt as normal numbers, such as +, -, /, *, %, etc.
Creating a value of type BigInt is also as simple as appending n to the number. For example, 123 becomes 123n. You can also use the global method BigInt(value) to convert, the input parameter value is a number or a number string.
const aNumber = 111;
const aBigInt = BigInt(aNumber);
aBigInt === 111n // true
typeof aBigInt === 'bigint' // true
typeof 111 // "number"
typeof 111n // "bigint"
Just add n to the end of the number to correctly calculate large numbers:
1234567890123456789n * 123n;
// -> 151851850485185185047n
There is one catch though, in most operations you can't mix BigInt with Number. Comparing Number and BigInt is OK, but not adding them.
1n < 2
// true
1n + 2
// Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
globalThis
globalThis is a new standard method to get global this . Previously, developers would obtain it through the following methods:
- The global variable window: is a classic way to get the global object. But it doesn't work with Node.js and Web Workers
- Global variable self: usually only takes effect in Web Workers and browsers. But it doesn't support Node.js. Some people will identify whether the code is running in Web Workers and browsers by judging whether self exists or not
- Global variable global: only valid in Node.js
To get the global object in the past, you can pass a global function:
// pre-ES10 solution
const getGlobal = function(){
if(typeof self !== 'undefined') return self
if(typeof window !== 'undefined') return window
if(typeof global !== 'undefined') return global
throw new Error('unable to locate global object')
}
// ES10 built-in
globalThis.Array(0,1,2) // [0,1,2]
// Define a global object v = { value: true } , ES10 defines it as follows
globalThis.v = { value: true }
The purpose of globalThis is to provide a standardized way to access global objects . With globalThis, you can get global objects in any context and at any time.
If you're on a browser, globalThis will be window, and if you're on Node, globalThis will be global. Therefore, it is no longer necessary to consider different environmental issues.
// worker.js
globalThis === self
// node.js
globalThis === global
// browser.js
globalThis === window
The new proposal also stipulates that Object.prototype must be in the prototype chain of the global object. The following code already returns true in recent browsers:
Object.prototype.isPrototypeOf(globalThis); // true