ECMAScript 2020 biggest new features
It’s June and this means that the new 11th edition of ECMA-262 standard defining the ECMAScript and thus JavaScript language will be out shortly. Now, as you might know from my previous article about ECMAScript and the one about ES2019 features, JavaScript, ever since the introduction of ES6, is experiencing kind-of a rolling release cycle. This means that while new editions of the ES specification go through the whole proposal, discussion, approval and finalization process, individual features often appear much earlier in different browsers than the yearly specification release.
With that said, it’s still a nice thing to have this moment in a year that you can say which new JS features are here for sure. And even though most web developers won’t use all those features right away due to compatibility concerns, it’s good to keep an eye on where the language is going.
And so, in this article, we’ll go over the biggest of the new features introduced with ES2020.
BigInt
You might have heard of it already. [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)
is a new 7th primitive data type in JavaScript and arguably the biggest new addition of ES2020. It’s meant to allow developers to work with some really big integers.
The biggest integer “usual” number
type can handle is equal to 2 ** 53 - 1
or 9007199254740991
. You can access this value under the MAX_SAFE_INTEGER
constant.
Number.MAX_SAFE_INTEGER; // 9007199254740991
As the name implies, operating on numbers above this value can be… quirky. With BigInt
s, there are no limits - except for your device’s memory.
To define a BigInt
you can either use the BigInt()
function with a string representation of your big value or syntax similar to usual numbers, but followed by n
.
const myBigInt = BigInt("999999999999999999999999999999");
const mySecondBigInt = 999999999999999999999999999999n;
typeof myBigInt; // "bigint"
It’s important to know that BigInt
s aren’t fully compatible with “usual” number
s. This means that you’ll most likely want to use BigInt
s only when you know for certain that you’ll be dealing with really big numbers.
const bigInt = 1n; // small number, but still of BigInt type
const num = 1;
num === bigInt; // false -> they aren't strictly equal
num == bigInt; // true
num >= bigInt; // true -> they can be compared
num + bigInt; // error -> they can't operate with one another
Overall, BigInt
s are great for all those who do some complex math with JS. They do a great job of replacing quirky and slow libraries dedicated to the sole purpose of working with big numbers. Or at least integers, as we still haven’t heard much of the BigDecimal
proposal.
As for the support, it’s already pretty good (for a new feature) with different Chromium-based browsers and Firefox having it for a few versions now. Only Safari lags behind.
Dynamic imports
Similarly to BigInt
s, dynamic imports is a feature you might be familiar with. Maybe that’s because it was introduced to V8 way back in late 2017!
Anyway, dynamic imports, as one might expect, are meant to allow easier code-splitting natively in the browser. Instead of bundling, loading your modules statically, or using some clever AJAX tricks, you can now use the import
keyword function-like syntax - import()
to load your modules dynamically.
import("module.js").then((module) => {
// ...
});
// or
async () => {
const module = await import("module.js");
};
The import()
results in a promise resolving with the content exported by the loaded module. Thus, it can be used either with the ES6 .then()
method, or the newer async
/await
.
Like I’ve said, the support is already very good across all the major browsers.
Nullish coalescing operator
Now we’re starting to talk about some truly new stuff! Nullish coalescing operator (??
) is a new JS operator allowing basically to provide a “default value” when the accessed one is either null
or undefined
. Check it out:
const basicValue = "test";
const nullishValue = null;
const firstExample = basicValue ?? "example"; // "test"
const secondExample = nullishValue ?? "example"; // "example"
Alright, but you might be asking - how does that differ from the logical OR operator aka double-pipe (||
)? Well, the difference is actually very simple. Logical OR would use the second operand every time the first one is determined to be falsy - which in JavaScript means false
, 0
, or ""
, while also counting in nullish values - null
and undefined
. On the other hand, nullish coalescing operator only uses the second operand when the first one is nullish - not falsy. Thus, if your code requires you to consider any value other than null
or undefined
as viable, then the this new operator is your best bet.
const falseValue = false;
const zeroValue = 0;
const emptyValue = "";
const nullishValue = null;
const firstExampleOR = falseValue || "example"; // "example"
const secondExampleOR = zeroValue || "example"; // "example"
const thirdExampleOR = emptyValue || "example"; // "example"
const forthExampleOR = nullish || "example"; // "example"
const firstExample = falseValue ?? "example"; // false
const secondExample = zeroValue ?? "example"; // 0
const thirdExample = emptyValue ?? "example"; // ""
const forthExample = nullish ?? "example"; // "example"
Support for this operator is pretty decent - most if not all of the latest versions of major browsers support it and additionally it can be easily transpiled with tools like Babel, or used with TypeScript.
Optional chaining operator
Similarly to the nullish coalescing operator, the optional chaining operator also deals with null
and undefined
- but this time in objects. Whereas previously trying to access a property on a nullish value would result in an error, now optional chaining operator (?.
) would simply continue to “return” the nullish value.
const obj = {
prop: {
subProp: {
value: 1,
},
},
};
obj.prop.subProp.value; // 1
obj.prop.secondSubProp.value; // error
obj?.prop?.subProp?.value; // 1
obj?.prop?.secondSubProp?.value; // undefined
Of course, this is nothing more than just some syntactic sugar but it’s a welcome addition nonetheless. Just remember to not flood your code with these operators - they’re nice but still have a very tiny higher impact on performance than a usual .
. And it’s even more so if you’re using this through transpilation in Babel or TypeScript, which is also possible.
As for the browser support - it’s basically the same as for nullish coalescing operator - so fine, but nothing special.
GlobalThis
Now, because of JavaScript’s omnipresence, the same code is often expected to work across many different environments, such as the browser, Node.js, or Web Workers. And even though achieving this cross-compatibility is never an easy task, it just got a little bit easier thanks to [globalThis](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis)
.
globalThis
is a new global property that always refers to the current environment’s default global object. This means self
for Web Workers, window
for browsers, global
for Node.js, and anything else for any runtime that correctly implements the ES2020 standard.
// Hacky globalThis polyfill you had to use pre-ES2020
const getGlobal = () => {
if (typeof self !== "undefined") {
return self;
}
if (typeof window !== "undefined") {
return window;
}
if (typeof global !== "undefined") {
return global;
}
throw new Error("Couldn't detect global");
};
getGlobal() === globalThis; // true (for browser, Web Worker and Node.js)
globalThis === window; // true (if you're in browser)
globalThis
is already pretty well-supported across all major browsers, and there are external polyfills available for use in older environments.
Promise.allSettled()
Like most previous releases, ES2020 not only adds entirely new features but also improves on the old ones. So is the case with Promises which gained new [Promise.allSettled()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled)
method. It’s similar to the already existent Promise.all()
method in a way that it returns a promise that resolves when all passed Promises are “settled”, but with 1 major difference. Unlike Promise.all()
, which resolves when all passed Promises resolves and fails when only a single Promise fails, Promise.allSettled()
resolves always when every Promise is settled - no matter if it resolved or failed. Hence the name.
const promises = [
new Promise(() => {
/* ... */
}),
/* ... */
];
Promise.allSettled(promises).then(() => {
console.log("All promises have settled!");
});
Promise.allSettled()
has good support and is polyfillable with libraries like core-js (only applicable to newer versions).
String.matchAll()
Another new improvement-like method is [String.matchAll()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/matchAll)
. Basically, if you’ve ever worked with RegExps before, String.matchAll()
is a nice alternative to using RegExp.exec()
in a while
loop with the g
flag enabled. That’s all there’s to it. It returns an iterator (not to be confused with full-blown arrays) that contains all the match results - including capturing groups.
const regexp = /t(e)(st(\d?))/g;
const str = "test1test2";
const resultsArr = [...str.matchAll(regexp)]; // convert iterator to an array
resultsArr[0]; // ["test1", "e", "st1", "1"]
resultsArr[0]; // ["test2", "e", "st2", "2"]
The support is good and the feature can be easily polyfilled with the method I described earlier.
For-in order
Lastly, we’ve got just a minor tweak to the specs that now strictly defines the order in which the for..in
loop should iterate. It was already handled pretty well by the browsers themselves, so it’s just a matter of making it official.
Bottom line
As you can see, there are some interesting new features when it comes to ES2020. Most of them have pretty good cross-browser support already and we can suspect that it’ll only get better as the time goes on. However, let’s face it - there are no “ground breaking” features in this release, and web developers are unlikely to utilize any of them to their full extent. The need to support older browsers is limiting, and when you factor in all the necessary work and polyfills and transpilation that your code would require, there’s simply not good enough reason to justify this trade-off.
So, if you support only the newest browsers I say good for you. Just use the new features however you want. But, if you want broader support, then I think the choice is up to you.
Anyway, if you enjoyed the content consider following me on Twitter, Facebook, or through my newsletter for more up-to-date web dev stuff. Thanks for reading and happy coding!
If you need
Custom Web App
I can help you get your next project, from idea to reality.