Writing JavaScript like it's 2020

January 08, 2020 – Clement Bruno – 9-minute read

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).

ES2020

Optional chaining operator

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

BigInt

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:

  • Operations between numbers are only possible if they share the same type:
100n / 2 // => TypeError: Cannot mix BigInt and other types, use explicit conversions
100n / 2n // => 50n
typeof(100n / 2n) // => 'bigint'
  • Since the output of operations involving BigInt numbers is itself a BigInt, fractional values are truncated:
25n / 2n // => 12n

Nullish coalescing Operator

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" // => ""

ES2019

I’d also like to mention two useful features that aren’t yet very widespread despite their usefulness which were brought with ES2019.

Array.flat()

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.

Object.fromEntries()

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']];

Conclusion

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.

Did you enjoy this post? Join Getaround's engineering team!
View openings