AppDevRocks

March 30, 2020 in

What's new in ES2019 (ES10)

Hey everybody, today we're going to look at the new features and changes introduced in ES2019 (also known as ES10) and see some practical use cases for each.

There's two new methods available to arrays: .flat() and .flatMap. The .flat() method will return a new array with any child arrays flattened:

Array.prototype.flat()

const numbers = [1, 2, 3, [4, 5, 6]]
console.log(numbers.flat())

// [1, 2, 3, 4, 5, 6]

What happens if we have child arrays within child arrays?

const numbers = [1, 2, 3, [4, 5, 6, [7, 8, 9]]]
console.log(numbers.flat())

// [1, 2, 3, 4, 5, 6, Array(3)]

and if we convert this to JSON using JSON.stringify() we'll see that the child array inside of the child array was left unflattened.

const numbers = [1, 2, 3, [4, 5, 6, [7, 8, 9]]]
console.log(JSON.stringify(numbers.flat()))

// [1, 2, 3, 4, 5, 6, [7, 8, 9]]

In other words, the .flat() method will only flatten one level deep. If you want to flatten deeper than that, the .flat() method has an optional parameter to specify how deep to flatten:

const numbers = [1, 2, 3, [4, 5, 6, [7, 8, 9]]]
console.log(numbers.flat(2))

// [1, 2, 3, 4, 5, 6, 7, 8, 9]

Array.prototype.flatMap()

With the .flatMap() method, if you're familiar with the .map() method, this is essentially identical to .map() followed by the .flat() method. In this example you can see I'm passing in a function which just returns whatever value it receives:

const numbers = [1, 2, 3, [4, 5, 6, [7, 8, 9]]]
console.log(numbers.flatMap(a => a))

// [1, 2, 3, 4, 5, 6, Array(3)]

As you can see there's no advantage beyond .flat() in this case. If we expand it a bit further we can check each value to see if it's an array() and if it is, simply return it, otherwise multiple the value by 10:

const numbers = [1, 2, 3, [4, 5, 6, [7, 8, 9]]]
console.log(numbers.flatMap(a => Array.isArray(a) ? a : a * 10))

// [10, 20, 30, 4, 5, 6, Array(3)]

Again, this is equivalent to calling .map() followed by .flat():

const numbers = [1, 2, 3, [4, 5, 6, [7, 8, 9]]]
console.log(numbers.map(a => Array.isArray(a) ? a : a * 10).flat())

// [10, 20, 30, 4, 5, 6, Array(3)]

Object.fromEntries()

The Object.fromEntries() method will take an array of of key-value pairs and convert them into an object.

const myObj = Object.fromEntries([ ['one', 1], ['two', 2] ])
console.log(myObj)

// {one: 1, two: 2}

You can also pass in a Map instead of an array for the same result:

const myMap = new Map([ ['one', 1], ['two', 2] ])
const myObj = Object.fromEntries(myMap)
console.log(myObj)

// {one: 1, two: 2}

String.prototype.trimStart() and String.prototype.trimEnd()

Strings have been given two new methods: .trimStart() and .trimEnd(). You're probably already familiar with the .trim() method which removes whitespace characters from either end of the string. As you can probably guess, .trimStart() will remove whitespaces only from the start of the string:

const trimmedString = "   hello world   ".trimStart()
console.log(trimmedString)

// "hello world   "

Likewise, .trimEnd() removes whitespace characters only from the end of the string:

const trimmedString = "   hello world   ".trimEnd()
console.log(trimmedString)

// "   hello world"

Just like .trim(), these functions remove all whitespace characters, like spaces, tabs, and line endings.

const trimmedString = "   hello world \n \t ".trimEnd()
console.log(trimmedString)

// "   hello world"

Try Catch Optional Binding

Next we have optional try-catch binding. You've probably wrote some try-catch code like this before:

const badFunction = () => { throw new Error() }

try {
  badFunction()
} catch(e) {
  console.log('Something happened')
}

Notice how in the catch, even though I don't actually need to use the error parameter "e", I was still required to include that part, but not anymore. Now, you can leave that part out if you don't intend to use it:

const badFunction = () => { throw new Error() }

try {
  badFunction()
} catch {
  console.log('Something happened')
}

Revised Function.prototype.toString()

You might have used the function method .toString() which prints out the source code of the function, but now it will even preserve the indentation and comments:

function sum(a, b) { 
  // add the params
  return a + b 
}

console.log(sum.toString())

// function sum(a, b) { 
//   // add the params
//   return a + b 
// }

Symbol.prototype.description()

Symbols now have a description property which will return the originally assigned value:

const mySymbol = Symbol("This is my symbol")
console.log(mySymbol.description)

// "This is my symbol"

This can make it a bit easier to debug when you're working with many symbols.

Stable Array.prototype.sort()

The Array.Sort method used to switch to use a QuickSort algorithm when used on arrays with more than 10 elements. When used on an array of objects to sort on a specific property, QuickSort could lead to elements with matching properties to be out of the originally provided order. Imagine we're sorting an array of users by their score, and there are two objects with the same score:

{ name: 'Alex', score: 5 }, { name: 'Bob', score: 5 }

Well-formed JSON.stringify()

This next one is a bit more specialized. If you've tried to run JSON.stringify() with a single UTF-8 code point, instead of a traditional pair, you would end up with malformed characters. Now, JSON.stringify() will preserve those single code points:

JSON.stringify('\uDFFF')

// "\udfff"

Since they're the same value, the order of the objects shouldn't be modified, but with QuickSort it could. Now, they've switched to the TimSort algorithm, which provides consistent results.

JSON Subset of ECMAScript

Finally, ECMAScript string literals couldn’t contain the characters for Line and Paragraph Separators without an escape sequence, but JSON string literals could. This lead to syntax errors if you've tried to use JSON.parse() on those strings. Now JSON is a subset of ECMAScript, which means you're allowed to include those characters in ECMAScript and convert freely between JSON and plain old strings:

JSON.parse('"\u2028"')
JSON.parse('"\u2029"')

Browser Support

Browser support for these features is looking good. The latest version of the major web browsers support all of these features, with some exceptions around Safari's handling of Function.toString(). The latest Babel is lacking the Function.toString and JSON superset updates, but are likely covered with polyfills. Node.js 12 and up also has full coverage of these features. For more detailed engine support, you can check out the compatibility grid on Kangax's page: https://kangax.github.io/compat-table/es2016plus

Conclusion

That wraps up our look at the new features added in ES2019. I hope you found this article valuable and are starting to think of ways to use these new features in your code.


Continue the Discussion

© 2020 Nick Gagne. All rights reserved.