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!
Comments (23)
I think you forgot to use the value from aux in the ES5 swap example
Yup. Fixed. Thanks guys! Slight oversight.
It seems that:
Should be:
Yup. Fixed. Thanks guys! Slight oversight.
Sometimes it gets even worse with left = [right,right=left][0]
#facepalm
It’s getting better. But keeping ES5 backwards portability with ES5 > ES6 code would be nicer.
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 casevar foo = obj[key]is sufficient.Yeah. I actually wanted to showcase computed property names, and not so much destructuring. This happens a lot in ES5:
With ES6 you can just do:
Should’ve made the point more clear and concise, sorry about that!
I don’t think nested destructing works for import statements:
https://babeljs.io/repl/#?experimental=true&evaluate=true&loose=false&spec=false&playground=true&code=import {series%2C concurrent%2C map%3A { series%3A mapSeries } } from ‘contra’
You’re right! That’s one of the few methods I didn’t actually run through
babel-node. My bad :)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.)
Martin, I’m pretty sure you’re right in that they aren’t the same but look similar!
I’ve updated the article to reflect these issues. Thanks for pointing them out!
I have a little concern about in my opinion is not really intuitive. Why do we have:
instead of:
? I’m sure there have to be a reason, that’s why I ask.
The way it is makes it easier to reason about the shorthand syntax:
Which is equivalent to:
This may be useful somewhere:
Did ES6 do away with semicolon line endings? I notice that you left them out of your code.
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:
Without the semi-colons, it was being as interpreted as:
Thought that was a funny little curiosity, mostly because it’s not something you’d really encounter before.
the content below:
I try in babel-node, it’s thows
I just don’t understand why, can you help ~ thks~
You can use destructuring too when you are working with ReactJS, when you have to work with the props of a component.
Your example of extracting dynamic keys is slightly misleading. You show ES6 done in 3 lines of code:
And then you show ES5 in 4 lines of code:
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:
Or alternatively, use a variable in ES6 example:
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.es6 i hate,so much confuse,not clear,someone agree with me?