ponyfoo.com

ES6 JavaScript Destructuring in Depth

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.

Improve this article
Nicolás Bevacqua
| 10 minute read | 23

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 (23)

ahmed wrote

I think you forgot to use the value from aux in the ES5 swap example

Nicolas Bevacqua wrote

Yup. Fixed. Thanks guys! Slight oversight.

Vitor Buzinaro wrote

It seems that:

if (right > left) {
  aux = right
  right = left
  left = right
}

Should be:

if (right > left) {
  aux = right
  right = left
  left = aux
}
Nicolas Bevacqua wrote

Yup. Fixed. Thanks guys! Slight oversight.

Boris Kozorovitzky wrote

Sometimes it gets even worse with left = [right,right=left][0]

#facepalm

Flavio Kodama wrote

It’s getting better. But keeping ES5 backwards portability with ES5 > ES6 code would be nicer.

Šime Vidas wrote

The 'such_dynamic' example is a bit of a stretch. Why would you define an object via object literal, just to pull out a dynamic key value and then immediately discard the object? It seems more likely that the object would be pre-existing (in outer scope or function argument), in which case var foo = obj[key] is sufficient.

Nicolas Bevacqua wrote

Yeah. I actually wanted to showcase computed property names, and not so much destructuring. This happens a lot in ES5:

var a = {}
a[type] = value
return a

With ES6 you can just do:

return { [type]: value }

Should’ve made the point more clear and concise, sorry about that!

Moshe Kolodny wrote
Nicolas Bevacqua wrote

You’re right! That’s one of the few methods I didn’t actually run through babel-node. My bad :)

Martin Segado wrote

Actually, I’m pretty sure imports aren’t an example of object destructuring at all - they just share similar syntax. (Double check on that in case I’m wrong though.)

Henry Zhu wrote

Martin, I’m pretty sure you’re right in that they aren’t the same but look similar!

Nicolas Bevacqua wrote

I’ve updated the article to reflect these issues. Thanks for pointing them out!

Diana wrote

I have a little concern about in my opinion is not really intuitive. Why do we have:

var foo = { bar: 'pony', baz: 3 }
var {bar: a, baz: b} = foo

instead of:

var foo = { bar: 'pony', baz: 3 }
var {a: bar, b: baz} = foo

? I’m sure there have to be a reason, that’s why I ask.

Nicolas Bevacqua wrote

The way it is makes it easier to reason about the shorthand syntax:

var foo = { bar: 'pony', baz: 3 }
var {bar, baz} = foo

Which is equivalent to:

var foo = { bar: 'pony', baz: 3 }
var {bar: bar, baz: baz} = foo
Riophae wrote

This may be useful somewhere:

function test(key, { [key]: value }, obj) {
  return { [key]: { ...value, ...obj } };
}
Berkana wrote

Did ES6 do away with semicolon line endings? I notice that you left them out of your code.

roshow wrote

No, it did not and I learned this the hard way. Don’t remember my exactly code but essentially it boiled down to this structure:

var a = 10
var b = 20
var foo = { bar: 10, baz: 10 }
[b, a] = [a,b]

Without the semi-colons, it was being as interpreted as:

{bar: 10, baz: 10}[b,a]

Thought that was a funny little curiosity, mostly because it’s not something you’d really encounter before.

kugua wrote

the content below:

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 

I try in babel-node, it’s thows

var bar = _baz.foo.bar;
TypeError: Cannot read property 'bar' of undefined

I just don’t understand why, can you help ~ thks~

David Castelblanco wrote

You can use destructuring too when you are working with ReactJS, when you have to work with the props of a component.

var  { num, handler } = this.props;
Daniel Gruszczyk wrote

Your example of extracting dynamic keys is slightly misleading. You show ES6 done in 3 lines of code:

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

And then you show ES5 in 4 lines of code:

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

However, I am unsure why are you working directly on an object in one example and you are assigning an object to a variable and then operating on a variable in another. the ES5 should be done in the following way to make the comparison more representative:

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

Or alternatively, use a variable in ES6 example:

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

I don’t think the module syntax part suitable for this article, it’s confusing and it belongs to another entire article maybe ES6 Module.

buuug7 wrote

es6 i hate,so much confuse,not clear,someone agree with me?