JavaScript (JS) has long been criticized for being verbose and quirky. But the recent additions made to the language allow us to cope nicely with some of the debatable design decisions that were made and even benefit from a truly enjoyable development experience. In fact JS boasts a vast ecosystem, is present in a wide array of development use cases and is improved each and every year with excellent features.
In the following article we explore some of these great features that were added to the language with ES2019 and ES2020.
NB: The following list does not aim at being exhaustive but merely at describing some of the new stuff I am enthusiastic about.
Additionally, most of these new features are not yet supported by the major browsers but are already usable if you use the Babel transpiler or a recent version of TypeScript (>= 3.7).
This one probably is among my favorites because it allows to vastly reduce the amount of code written when dealing with complex objects and when being unsure about the content structure.
For instance, if we take the following example:
const userInfos = {
name: 'Bob',
email: 'bob@getaround.com',
familyMembers: {
brother: {
name: 'Bobby',
age: 16
},
mother: {
name: 'Constance',
age: 55
},
father: {
name: 'John',
age: 60
}
}
}
console.log(
userInfos.familyMembers
&& userInfos.familyMembers.mother
&& userInfos.familyMembers.mother.age
);
As you can see above retrieving the mother’s age was a pain and the nesting level is not even that deep. Developers already tried to solve this issue in the past and some interesting solutions such as the delve
utility function were developed. But the optional chaining operator is now a native implementation of the wanted behaviour and removes the dependency to external packages which is always appreciated.
Using this new operator allows us to reduce the code to:
console.log(userInfos.familyMembers?.mother?.age); // => 55
In case the value called after ?
is not found the program won’t crash and will return undefined
instead.
console.log(userInfos.familyMembers?.grandPa?.age); // => undefined
The addition of BigInt
to JS primitives is a good thing since, for long, manipulating numbers in JS was considered hazardous. The Number
type is capped to integer values of 2**53-1 which can be really limiting.
console.log(Number.MAX_SAFE_INTEGER) // => 9007199254740991
This addition provides real built-in support for manipulating large numbers and using this new type is trivial. Just adding n
at the end of a number makes it a BigInt
.
// BigInt numbers can be declared like that:
const bigIntNum = 100n;
const bigIntNum2 = BigInt(100);
const bigIntNum3 = BigInt("100");
There are some limitations though:
100n / 2 // => TypeError: Cannot mix BigInt and other types, use explicit conversions
100n / 2n // => 50n
typeof(100n / 2n) // => 'bigint'
BigInt
numbers is itself a BigInt
, fractional values are truncated:25n / 2n // => 12n
I am really looking forward for this one to be widespread because it is also a great feature that fixes the flaws of the infamous ||
.
As a reminder previously the ||
operator could produce surprising result because it would consider all falsy values…
const someValue = null || "some value" // => "some value"
The above example is perfectly valid but I am way less comfortable with the following 2 lines:
const someNumber = 0 || 300 // => 300
const someStr = "" || "some string" // => "some string"
This is due to the fact that, contrarily to what we have in most other languages, in JavaScript:
!!0 // => false
!!"" // => false
Using the new ??
operator solves such issue since it only deals with null
and undefined
values instead of all the falsy ones:
const someValue = null ?? "some value" // => "some value"
const someNumber = 0 ?? 300 // => 0
const someStr = "" && "some string" // => ""
I’d also like to mention two useful features that aren’t yet very widespread despite their usefulness which were brought with ES2019.
flat
as its name indicates allows to reduce progressively the nesting of imbricated lists:
const nestedList = ["first level", ["second level", ["third level"]]];
console.log(nestedList.flat()); // => [ 'first level', 'second level', [ 'third level' ] ]
It takes an optional parameter that specifies how many level of nesting should be unnested.
console.log(nestedList.flat(2)); // => [ 'first level', 'second level', 'third level' ]
When no parameter is provided the function defaults to 1. Therefore it is equivalent to write:
console.log(nestedList.flat()); // => [ 'first level', 'second level', [ 'third level' ] ]
console.log(nestedList.flat(1)); // => [ 'first level', 'second level', [ 'third level' ] ]
If you are dealing with a data structure with an unknown level of nesting and are sure you want everything unnested you can provide Infinity
as an argument to the flat
function.
console.log(nestedList.flat(Infinity)); // => [ 'first level', 'second level', 'third level' ]
NB: Please note that a flatMap
function was also introduced and as its name indicates it combines the flat
described above with a map
loop method.
It is also worth mentioning that, in the functional programming spirit, these new methods do no mutate the array on which they are called but create a new one.
This new feature is interesting to switch from one data structure to another to best fit our development need.
For instance, given a nested lists data structure:
const someList = [['age', 55], ['name', 'bob'], ['email', 'bob@bobby.com']];
if I needed to access the “bob” value, I’d have to write something like:
console.log(someList[1][1]); // => "bob"
This works but it is flawed because it relies on the list content ordering and it does not provide any context regarding the fact that I want to access the name
property.
With the the new Object.fromEntries()
function we can adopt a very much cleaner approach:
const someList = [['age', 55], ['name', 'bob'], ['email', 'bob@bobby.com']];
const someObject = Object.fromEntries(someList);
console.log(someObject); // => { age: 55, name: 'bob', email: 'bob@bobby.com' }
console.log(someObject.name); // => "bob"
NB: The corollary feature is Object.entries()
which allows to transform an Object into a nested array structure.
const someObject = { age: 55, name: 'bob', email: 'bob@bobby.com' }
const someList = Object.entries(someObject);
console.log(someList); // => [['age', 55], ['name', 'bob'], ['email', 'bob@bobby.com']];
The features described above are just a subset of the recent additions made to the language but these are very much likable because they allow us to write code that is much more readable and concise. As a consequence the whole development experience feels less hacky and really enjoyable overall.