{"id":293957,"date":"2019-08-13T07:32:34","date_gmt":"2019-08-13T14:32:34","guid":{"rendered":"https:\/\/css-tricks.com\/?p=293957"},"modified":"2019-08-14T06:03:32","modified_gmt":"2019-08-14T13:03:32","slug":"all-the-new-es2019-tips-and-tricks","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/all-the-new-es2019-tips-and-tricks\/","title":{"rendered":"All the New ES2019 Tips and Tricks"},"content":{"rendered":"

The ECMAScript standard has been updated yet again with the addition of new features in ES2019. Now officially available<\/a> in node, Chrome, Firefox, and Safari you can also use Babel<\/a> to compile these features to a different version of JavaScript if you need to support an older browser. <\/p>\n

Let\u2019s look at what\u2019s new!<\/p>\n

<\/p>\n

Object.fromEntries<\/h3>\n

In ES2017, we were introduced to Object.entries<\/code>. This was a function that translated an object into its array representation. Something like this:<\/p>\n

let students = {\r\n  amelia: 20,\r\n  beatrice: 22,\r\n  cece: 20,\r\n  deirdre: 19,\r\n  eloise: 21\r\n}\r\n\r\nObject.entries(students) \r\n\/\/ [\r\n\/\/  [ 'amelia', 20 ],\r\n\/\/  [ 'beatrice', 22 ],\r\n\/\/  [ 'cece', 20 ],\r\n\/\/  [ 'deirdre', 19 ],\r\n\/\/  [ 'eloise', 21 ]\r\n\/\/ ]<\/code><\/pre>\n

This was a wonderful addition because it allowed objects to make use of the numerous functions built into the Array prototype. Things like map<\/code>, filter<\/code>, reduce<\/code>, etc. Unfortunately, it required a somewhat manual process to turn that result back into an object.<\/p>\n

let students = {\r\n  amelia: 20,\r\n  beatrice: 22,\r\n  cece: 20,\r\n  deirdre: 19,\r\n  eloise: 21\r\n}\r\n\r\n\/\/ convert to array in order to make use of .filter() function\r\nlet overTwentyOne = Object.entries(students).filter(([name, age]) => {\r\n  return age >= 21\r\n}) \/\/ [ [ 'beatrice', 22 ], [ 'eloise', 21 ] ]\r\n\r\n\/\/ turn multidimensional array back into an object\r\nlet DrinkingAgeStudents = {}\r\nfor (let [name, age] of overTwentyOne) {\r\n    DrinkingAgeStudents[name] = age;\r\n}\r\n\/\/ { beatrice: 22, eloise: 21 }<\/code><\/pre>\n

Object.fromEntries<\/code> is designed to remove that loop! It gives you much more concise code that invites you to make use of array prototype methods on objects.<\/p>\n

let students = {\r\n  amelia: 20,\r\n  beatrice: 22,\r\n  cece: 20,\r\n  deirdre: 19,\r\n  eloise: 21\r\n}\r\n\r\n\/\/ convert to array in order to make use of .filter() function\r\nlet overTwentyOne = Object.entries(students).filter(([name, age]) => {\r\n  return age >= 21\r\n}) \/\/ [ [ 'beatrice', 22 ], [ 'eloise', 21 ] ]\r\n\r\n\/\/ turn multidimensional array back into an object\r\nlet DrinkingAgeStudents = Object.fromEntries(overTwentyOne); \r\n\/\/ { beatrice: 22, eloise: 21 }<\/code><\/pre>\n

It is important to note that arrays and objects are different data structures for a reason. There are certain cases in which switching between the two will cause data loss. The example below of array elements that become duplicate object keys is one of them. <\/p>\n

let students = [\r\n  [ 'amelia', 22 ], \r\n  [ 'beatrice', 22 ], \r\n  [ 'eloise', 21], \r\n  [ 'beatrice', 20 ]\r\n]\r\n\r\nlet studentObj = Object.fromEntries(students); \r\n\/\/ { amelia: 22, beatrice: 20, eloise: 21 }\r\n\/\/ dropped first beatrice!<\/code><\/pre>\n

When using these functions make sure to be aware of the potential side effects.<\/p>\n

Support for Object.fromEntries<\/h4>\n\n\n\n\n\n
Chrome<\/th>\nFirefox<\/th>\nSafari<\/th>\nEdge<\/th>\n<\/tr>\n<\/thead>\n
75<\/td>\n67<\/td>\n12.1<\/td>\nNo<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n

🔍 We can use your help.<\/strong> Do you have access to testing these and other features in mobile browsers? Leave a comment with your results — we’ll check them out and include them in the article.<\/p>\n

Array.prototype.flat<\/h3>\n

Multi-dimensional arrays are a pretty common data structure to come across, especially when retrieving data. The ability to flatten it is necessary. It was always possible, but not exactly pretty.<\/p>\n

Let\u2019s take the following example where our map leaves us with a multi-dimensional array that we want to flatten.<\/p>\n

let courses = [\r\n  {\r\n    subject: \"math\",\r\n    numberOfStudents: 3,\r\n    waitlistStudents: 2,\r\n    students: ['Janet', 'Martha', 'Bob', ['Phil', 'Candace']]\r\n  },\r\n  {\r\n    subject: \"english\",\r\n    numberOfStudents: 2,\r\n    students: ['Wilson', 'Taylor']\r\n  },\r\n  {\r\n    subject: \"history\",\r\n    numberOfStudents: 4,\r\n    students: ['Edith', 'Jacob', 'Peter', 'Betty']\r\n  }\r\n]\r\n\r\nlet courseStudents = courses.map(course => course.students)\r\n\/\/ [\r\n\/\/   [ 'Janet', 'Martha', 'Bob', [ 'Phil', 'Candace' ] ],\r\n\/\/   [ 'Wilson', 'Taylor' ],\r\n\/\/   [ 'Edith', 'Jacob', 'Peter', 'Betty' ]\r\n\/\/ ]\r\n\r\n[].concat.apply([], courseStudents) \/\/ we're stuck doing something like this<\/code><\/pre>\n

In comes Array.prototype.flat<\/code>. It takes an optional argument of depth. <\/p>\n

let courseStudents = [\r\n  [ 'Janet', 'Martha', 'Bob', [ 'Phil', 'Candace' ] ],\r\n  [ 'Wilson', 'Taylor' ],\r\n  [ 'Edith', 'Jacob', 'Peter', 'Betty' ]\r\n]\r\n\r\nlet flattenOneLevel = courseStudents.flat(1)\r\nconsole.log(flattenOneLevel)\r\n\/\/ [\r\n\/\/   'Janet',\r\n\/\/   'Martha',\r\n\/\/   'Bob',\r\n\/\/   [ 'Phil', 'Candace' ],\r\n\/\/   'Wilson',\r\n\/\/   'Taylor',\r\n\/\/   'Edith',\r\n\/\/   'Jacob',\r\n\/\/   'Peter',\r\n\/\/   'Betty'\r\n\/\/ ]\r\n\r\nlet flattenTwoLevels = courseStudents.flat(2)\r\nconsole.log(flattenTwoLevels)\r\n\/\/ [\r\n\/\/   'Janet',   'Martha',\r\n\/\/   'Bob',     'Phil',\r\n\/\/   'Candace', 'Wilson',\r\n\/\/   'Taylor',  'Edith',\r\n\/\/   'Jacob',   'Peter',\r\n\/\/   'Betty'\r\n\/\/ ]<\/code><\/pre>\n

Note that if no argument is given, the default depth is one. This is incredibly important because in our example that would not fully flatten the array. <\/p>\n

let courseStudents = [\r\n  [ 'Janet', 'Martha', 'Bob', [ 'Phil', 'Candace' ] ],\r\n  [ 'Wilson', 'Taylor' ],\r\n  [ 'Edith', 'Jacob', 'Peter', 'Betty' ]\r\n]\r\n\r\nlet defaultFlattened = courseStudents.flat()\r\nconsole.log(defaultFlattened)\r\n\/\/ [\r\n\/\/   'Janet',\r\n\/\/   'Martha',\r\n\/\/   'Bob',\r\n\/\/   [ 'Phil', 'Candace' ],\r\n\/\/   'Wilson',\r\n\/\/   'Taylor',\r\n\/\/   'Edith',\r\n\/\/   'Jacob',\r\n\/\/   'Peter',\r\n\/\/   'Betty'\r\n\/\/ ]<\/code><\/pre>\n

The justification for this decision is that the function is not greedy by default and requires explicit instructions to operate as such. For an unknown depth with the intention of fully flattening the array the argument of Infinity can be used.<\/p>\n

let courseStudents = [\r\n  [ 'Janet', 'Martha', 'Bob', [ 'Phil', 'Candace' ] ],\r\n  [ 'Wilson', 'Taylor' ],\r\n  [ 'Edith', 'Jacob', 'Peter', 'Betty' ]\r\n]\r\n\r\nlet alwaysFlattened = courseStudents.flat(Infinity)\r\nconsole.log(alwaysFlattened)\r\n\/\/ [\r\n\/\/   'Janet',   'Martha',\r\n\/\/   'Bob',     'Phil',\r\n\/\/   'Candace', 'Wilson',\r\n\/\/   'Taylor',  'Edith',\r\n\/\/   'Jacob',   'Peter',\r\n\/\/   'Betty'\r\n\/\/ ]<\/code><\/pre>\n

As always, greedy operations should be used judiciously and are likely not a good choice if the depth of the array is truly unknown.<\/p>\n

Support for Array.prototype.flat<\/h4>\n\n\n\n\n\n
Chrome<\/th>\nFirefox<\/th>\nSafari<\/th>\nEdge<\/th>\n<\/tr>\n<\/thead>\n
75<\/td>\n67<\/td>\n12<\/td>\nNo<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n\n\n\n\n\n
Chrome Android<\/th>\nFirefox Android<\/th>\niOS Safari<\/th>\nIE Mobile<\/th>\nSamsung Internet<\/th>\nAndroid Webview<\/th>\n<\/tr>\n<\/thead>\n
75<\/td>\n67<\/td>\n12.1<\/td>\nNo<\/td>\nNo<\/td>\n67<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n

Array.prototype.flatMap<\/h3>\n

With the addition of flat we also got the combined function of Array.prototype.flatMap<\/code>. We’ve actually already seen an example of where this would be useful above, but let’s look at another one. <\/p>\n

What about a situation where we want to insert elements into an array. Prior to the additions of ES2019, what would that look like?<\/p>\n

let grades = [78, 62, 80, 64]\r\n\r\nlet curved = grades.map(grade => [grade, grade + 7])\r\n\/\/ [ [ 78, 85 ], [ 62, 69 ], [ 80, 87 ], [ 64, 71 ] ]\r\n\r\nlet flatMapped = [].concat.apply([], curved) \/\/ now flatten, could use flat but that didn't exist before either\r\n\/\/ [\r\n\/\/  78, 85, 62, 69,\r\n\/\/  80, 87, 64, 71\r\n\/\/ ]<\/code><\/pre>\n

Now that we have Array.prototype.flat<\/code> we can improve this example slightly.<\/p>\n

let grades = [78, 62, 80, 64]\r\n\r\nlet flatMapped = grades.map(grade => [grade, grade + 7]).flat()\r\n\/\/ [\r\n\/\/  78, 85, 62, 69,\r\n\/\/  80, 87, 64, 71\r\n\/\/ ]<\/code><\/pre>\n

But still, this is a relatively popular pattern, especially in functional programming. So having it built into the array prototype is great. With flatMap<\/code> we can do this:<\/p>\n

let grades = [78, 62, 80, 64]\r\n\r\nlet flatMapped = grades.flatMap(grade => [grade, grade + 7]);\r\n\/\/ [\r\n\/\/  78, 85, 62, 69,\r\n\/\/  80, 87, 64, 71\r\n\/\/ ]<\/code><\/pre>\n

Now, remember that the default argument for Array.prototype.flat<\/code> is one. And flatMap<\/code> is the equivalent of combing map<\/code> and flat<\/code> with no argument. So flatMap<\/code> will only flatten one level.<\/p>\n

let grades = [78, 62, 80, 64]\r\n\r\nlet flatMapped = grades.flatMap(grade => [grade, [grade + 7]]);\r\n\/\/ [\r\n\/\/   78, [ 85 ],\r\n\/\/   62, [ 69 ],\r\n\/\/   80, [ 87 ],\r\n\/\/   64, [ 71 ]\r\n\/\/ ]<\/code><\/pre>\n

Support for Array.prototype.flatMap<\/h4>\n\n\n\n\n\n
Chrome<\/th>\nFirefox<\/th>\nSafari<\/th>\nEdge<\/th>\n<\/tr>\n<\/thead>\n
75<\/td>\n67<\/td>\n12<\/td>\nNo<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n\n\n\n\n\n
Chrome Android<\/th>\nFirefox Android<\/th>\niOS Safari<\/th>\nIE Mobile<\/th>\nSamsung Internet<\/th>\nAndroid Webview<\/th>\n<\/tr>\n<\/thead>\n
75<\/td>\n67<\/td>\n12.1<\/td>\nNo<\/td>\nNo<\/td>\n67<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n

String.trimStart and String.trimEnd<\/h3>\n

Another nice addition in ES2019 is an alias that makes some string function names more explicit. Previously, String.trimRight<\/code> and String.trimLeft<\/code> were available.<\/p>\n

let message = \"   Welcome to CS 101    \"\r\nmessage.trimRight()\r\n\/\/ '   Welcome to CS 101'\r\nmessage.trimLeft()\r\n\/\/ 'Welcome to CS 101   '\r\nmessage.trimRight().trimLeft()\r\n\/\/ 'Welcome to CS 101'<\/code><\/pre>\n

These are great functions, but it was also beneficial to give them names that more aligned with their purpose. Removing starting space and ending space. <\/p>\n

let message = \"   Welcome to CS 101    \"\r\nmessage.trimEnd()\r\n\/\/ '   Welcome to CS 101'\r\nmessage.trimStart()\r\n\/\/ 'Welcome to CS 101   '\r\nmessage.trimEnd().trimStart()\r\n\/\/ 'Welcome to CS 101'<\/code><\/pre>\n

Support for String.trimStart and String.trimEnd<\/h4>\n\n\n\n\n\n
Chrome<\/th>\nFirefox<\/th>\nSafari<\/th>\nEdge<\/th>\n<\/tr>\n<\/thead>\n
75<\/td>\n67<\/td>\n12<\/td>\nNo<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n

Optional catch binding<\/h3>\n

Another nice feature in ES2019 is making an argument in try-catch blocks optional. Previously, all catch blocks passed in the exception as a parameter. That meant that it was there even when the code inside the catch block ignored it.<\/p>\n

try {\r\n  let parsed = JSON.parse(obj)\r\n} catch(e) {\r\n  \/\/ ignore e, or use\r\n  console.log(obj)\r\n}<\/code><\/pre>\n

This is no longer the case. If the exception is not used in the catch block, then nothing needs to be passed in at all. <\/p>\n

try {\r\n  let parsed = JSON.parse(obj)\r\n} catch {\r\n  console.log(obj)\r\n}<\/code><\/pre>\n

This is a great option if you already know what the error is and are looking for what data triggered it.<\/p>\n

Support for Optional Catch Binding<\/h4>\n\n\n\n\n\n
Chrome<\/th>\nFirefox<\/th>\nSafari<\/th>\nEdge<\/th>\n<\/tr>\n<\/thead>\n
75<\/td>\n67<\/td>\n12<\/td>\nNo<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n

Function.toString() changes<\/h3>\n

ES2019 also brought changes to the way Function.toString()<\/code> operates. Previously, it stripped white space entirely.<\/p>\n

function greeting() {\r\n  const name = 'CSS Tricks'\r\n  console.log(`hello from ${name}`)\r\n}\r\n\r\ngreeting.toString()\r\n\/\/'function greeting() {\\nconst name = \\'CSS Tricks\\'\\nconsole.log(`hello from ${name} \/\/`)\\n}'<\/code><\/pre>\n

Now it reflects the true representation of the function in source code.<\/p>\n

function greeting() {\r\n  const name = 'CSS Tricks'\r\n  console.log(`hello from ${name}`)\r\n}\r\n\r\ngreeting.toString()\r\n\/\/ 'function greeting() {\\n' +\r\n\/\/  \"  const name = 'CSS Tricks'\\n\" +\r\n\/\/  '  console.log(`hello from ${name}`)\\n' +\r\n\/\/  '}'<\/code><\/pre>\n

This is mostly an internal change, but I can\u2019t help but think this might also make the life easier of a blogger or two down the line.<\/p>\n

Support for Function.toString<\/h4>\n\n\n\n\n\n
Chrome<\/th>\nFirefox<\/th>\nSafari<\/th>\nEdge<\/th>\n<\/tr>\n<\/thead>\n
75<\/td>\n60<\/td>\n12 – Partial<\/a><\/td>\n17 – Partial<\/a><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n
\n

And there you have it! The main feature additions to ES2019.<\/p>\n

There are also a handful of other additions that you may want to explore. Those include:<\/p>\n