ponyfoo.com

ES6 JavaScript Destructuring in Depth

Fix
A relevant ad will be displayed here soon. These ads help pay for my hosting.
Please consider disabling your ad blocker on Pony Foo. These ads help pay for my hosting.
You can support Pony Foo directly through Patreon or via PayPal.

I’ve briefly mentioned a few ES6 features (and how to get started with Babel) in the React article series I’ve been writing about, and now I want to focus on the language features themselves. I’ve read a ton about ES6 and ES7 and it’s about time we started discussing ES6 and ES7 features here in Pony Foo.

This article warns about going overboard with ES6 language features. Then we’ll start off the series by discussing about Destructuring in ES6, and when it’s most useful, as well as some of its gotchas and caveats.

A word of caution

When uncertain, chances are you probably should default to ES5 and older syntax instead of adopting ES6 just because you can. By this I don’t mean that using ES6 syntax is a bad idea – quite the opposite, see I’m writing an article about ES6! My concern lies with the fact that when we adopt ES6 features we must do it because they’ll absolutely improve our code quality, and not just because of the “cool factor” – whatever that may be.

The approach I’ve been taking thus far is to write things in plain ES5, and then adding ES6 sugar on top where it’d genuinely improve my code. I presume over time I’ll be able to more quickly identify scenarios where a ES6 feature may be worth using over ES5, but when getting started it might be a good idea not to go overboard too soon. Instead, carefully analyze what would fit your code best first, and be mindful of adopting ES6.

This way, you’ll learn to use the new features in your favor, rather than just learning the syntax.

Onto the cool stuff now!

Destructuring

This is easily one of the features I’ve been using the most. It’s also one of the simplest. It binds properties to as many variables as you need and it works with both Arrays and Objects.

var foo = { bar: 'pony', baz: 3 }
var {bar, baz} = foo
console.log(bar)
// <- 'pony'
console.log(baz)
// <- 3

It makes it very quick to pull out a specific property from an object. You’re also allowed to map properties into aliases as well.

var foo = { bar: 'pony', baz: 3 }
var {bar: a, baz: b} = foo
console.log(a)
// <- 'pony'
console.log(b)
// <- 3

You can also pull properties as deep as you want, and you could also alias those deep bindings.

var foo = { bar: { deep: 'pony', dangerouslySetInnerHTML: 'lol' } }
var {bar: { deep, dangerouslySetInnerHTML: sure }} = foo
console.log(deep)
// <- 'pony'
console.log(sure)
// <- 'lol'

By default, properties that aren’t found will be undefined, just like when accessing properties on an object with the dot or bracket notation.

var {foo} = {bar: 'baz'}
console.log(foo)
// <- undefined

If you’re trying to access a deeply nested property of a parent that doesn’t exist, then you’ll get an exception, though.

var {foo:{bar}} = {baz: 'ouch'}
// <- Exception

That makes a lot of sense, if you think of destructuring as sugar for ES5 like the code below.

var _temp = { baz: 'ouch' }
var bar = _temp.foo.bar
// <- Exception

A cool property of destructuring is that it allows you to swap variables without the need for the infamous aux variable.

function es5 () {
  var left = 10
  var right = 20
  var aux
  if (right > left) {
    aux = right
    right = left
    left = aux
  }
}
function es6 () {
  var left = 10
  var right = 20
  if (right > left) {
    [left, right] = [right, left]
  }
}

Another convenient aspect of destructuring is the ability to pull keys using computed property names.

var key = 'such_dynamic'
var { [key]: foo } = { such_dynamic: 'bar' }
console.log(foo)
// <- 'bar'

In ES5, that’d take an extra statement and variable allocation on your behalf.

var key = 'such_dynamic'
var baz = { such_dynamic: 'bar' }
var foo = baz[key]
console.log(foo)

You can also define default values, for the case where the pulled property evaluates to undefined.

var {foo=3} = { foo: 2 }
console.log(foo)
// <- 2
var {foo=3} = { foo: undefined }
console.log(foo)
// <- 3
var {foo=3} = { bar: 2 }
console.log(foo)
// <- 3

Destructuring works for Arrays as well, as we mentioned earlier. Note how I’m using square brackets in the destructuring side of the declaration now.

var [a] = [10]
console.log(a)
// <- 10

Here, again, we can use the default values and follow the same rules.

var [a] = []
console.log(a)
// <- undefined
var [b=10] = [undefined]
console.log(b)
// <- 10
var [c=10] = []
console.log(c)
// <- 10

When it comes to Arrays you can conveniently skip over elements that you don’t care about.

var [,,a,b] = [1,2,3,4,5]
console.log(a)
// <- 3
console.log(b)
// <- 4

You can also use destructuring in a function's parameter list.

function greet ({ age, name:greeting='she' }) {
  console.log(`${greeting} is ${age} years old.`)
}
greet({ name: 'nico', age: 27 })
// <- 'nico is 27 years old'
greet({ age: 24 })
// <- 'she is 24 years old'

That’s roughly how you can use destructuring. What is destructuring good for?

Use Cases for Destructuring

There are many situations where destructuring comes in handy. Here’s some of the most common ones. Whenever you have a method that returns an object, destructuring makes it much terser to interact with.

function getCoords () {
  return {
    x: 10,
    y: 22
  }
}
var {x, y} = getCoords()
console.log(x)
// <- 10
console.log(y)
// <- 22

A similar use case but in the opposite direction is being able to define default options when you have a method with a bunch of options that need default values. This is particularly interesting as an alternative to named parameters in other languages like Python and C#.

function random ({ min=1, max=300 }) {
  return Math.floor(Math.random() * (max - min)) + min
}
console.log(random({}))
// <- 174
console.log(random({max: 24}))
// <- 18

If you wanted to make the options object entirely optional you could change the syntax to the following.

function random ({ min=1, max=300 } = {}) {
  return Math.floor(Math.random() * (max - min)) + min
}
console.log(random())
// <- 133

A great fit for destructuring are things like regular expressions, where you would just love to name parameters without having to resort to index numbers. Here’s an example parsing a URL with a random RegExp I got on StackOverflow.

function getUrlParts (url) {
  var magic = /^(https?):\/\/(ponyfoo\.com)(\/articles\/([a-z0-9-]+))$/
  return magic.exec(url)
}
var parts = getUrlParts('http://ponyfoo.com/articles/es6-destructuring-in-depth')
var [,protocol,host,pathname,slug] = parts
console.log(protocol)
// <- 'http'
console.log(host)
// <- 'ponyfoo.com'
console.log(pathname)
// <- '/articles/es6-destructuring-in-depth'
console.log(slug)
// <- 'es6-destructuring-in-depth'

Special Case: import Statements

Even though import statements don’t follow destructuring rules, they behave a bit similarly. This is probably the “destructuring-like” use case I find myself using the most, even though it’s not actually destructuring. Whenever you’re writing module import statements, you can pull just what you need from a module’s public API. An example using contra:

import {series, concurrent, map } from 'contra'
series(tasks, done)
concurrent(tasks, done)
map(items, mapper, done)

Note that, however, import statements have a different syntax. When compared against destructuring, none of the following import statements will work.

  • Use defaults values such as import {series = noop} from 'contra'
  • “Deep” destructuring style like import {map: { series }} from 'contra'
  • Aliasing syntax import {map: mapAsync} from 'contra'

The main reason for these limitations is that the import statement brings in a binding, and not a reference or a value. This is an important differentiation that we’ll explore more in depth in a future article about ES6 modules.

I’ll keep posting about ES6 & ES7 features every day, so make sure to subscribe if you want to know more!

* How about we visit string interpolation tomorrow?
**We’ll leave arrow functions for monday!

Liked the article? Subscribe below to get an email when new articles come out! Also, follow @ponyfoo on Twitter and @ponyfoo on Facebook.
One-click unsubscribe, anytime. Learn more.

Comments