Upgrading Your Everyday JavaScript Magic With ES6 - Destructuring

There's always a better way 
to solve a problem
You just haven't found it...

...yet

  
        - Torvik Knivsa
        Alchemist

Welcome to the Future! ECMAScript 6

Welcome back JavaScriptmancer! So far in this first part of the book you’ve seen several great ES6 features:

In this and the upcoming chapters we will introduce other great ES6 features that you can use in your everyday JavaScript: destructuring, arrow functions and the spread operator. It’s time to upgrade your JavaScript wizardry to ES6!

Destructure All The Things!

ES6 comes with a very handy new feature called destructuring. Destructuring lets you extract parts of information from within objects and other data structures in a very natural and concise manner.

Destructuring Objects

In the simplest of use cases, you can initialize variables from properties of objects using the following syntax:

1 let pouch = {coins: 10};
2 let {coins} = pouch;

This is equivalent to:

1 let coins = pouch.coins;

Using destructuring in a real world example is not very different. Imagine that you have a cimmerian barbarian that is yearning to improve his JavaScript skills. Let’s call him conan:

 1 let conan = {
 2         firstName: 'Conan',
 3         lastName: 'the barbarian',
 4         height: 178,
 5         weight: 90,
 6         email: 'conan.thecimmerian@akilonia.com',
 7         toString() {
 8             return this.firstName;
 9         }
10     };

We can create a javascriptmancyCourse object that allows anyone, not just barbarians, to learn some javascript by using a helpful signUp method:

 1 let javascriptmancyCourse = {
 2     signUp(person){
 3       let {firstName, lastName} = conan;
 4       console.log(`Thank you ${firstName}, ${lastName}!! 
 5 You've successfully signed up to our very special JavaScript course!
 6 Welcome and prepare to learn some JavaScript!`);
 7 }};
 8 
 9 javascriptmancyCourse.signUp(conan);
10 // => Thank you Conan, the barbarian!! 
11 //    You've succesfully signed up to our very special 
12 //    JavaScriptmancy course! 
13 //    Welcome and prepare to learn some JavaScript!

The let {firstName, lastName} = conan lets us extract the information necessary from the conan object and have it ready for processing in a terse single statement. If you appreciate writing beautiful code it doesn’t get better than this.

And there’s more! You are not limited to using variables that have exactly the same names than the properties within the original object. You can take advantage of a slightly more advanced destructuring syntax to map an object property to a different variable:

1 let { lastName:title } = conan;

This is equivalent to:

1 let title = conan.lastName;

So that lastName is the name of the origin property and title is the name of the destination variable. Additionally, if you try to extract a property that doesn’t exist in the source object, the newly created variable will be undefined:

1 // let pouch = {coins: 10};
2 let {bills} = pouch;
3 console.log(bills);
4 // => undefined

To prevent this from happening you can use default values in tandem with destructuring syntax. With this powerful combination, if an object doesn’t have a given property, your variable still gets a default value instead of undefined.

1 // let pouch = {coins: 10};
2 let {bills=10} = pouch;

Using defaults with destructuring as depicted above will ensure that you’ll never be poor and enjoy those illusory 10 bills even when they are not in your pouch. Magic!

Yet another mighty feature of destructuring is the ability to extract properties that are deep within an object. Imagine that you have one of those useful bags that have infinite pockets and you want to reach for your tobacco pouch:

1 let bag = { 
2     leftPocket: {
3         tobaccoPouch: ['pipe', 'tobacco']
4     }, 
5     rightPocket: [pouch],
6     interior: ['10 pieces of dried meat', 'toilet paper', 'leprechau\
7 n']
8 };

Well you can create a new tobbacoPouch variable using destructuring. It’s this easy:

1 let {leftPocket: {tobbacoPouch}} = bag;
2 
3 console.log(`Let's see what I've got in my tobaccoPouch: 
4   ${tobaccoPouch}`);
5 // => Let's see what I've got in my tobaccoPouch: pipe,tobacco

You can read this like go into the leftPocket grab the tobaccoPouch and put it in its own separate variable.

Again if the property you are trying to extract doesn’t exist you’ll get undefined:

1 let {leftPocket: {secretPouch}} = bag;
2 console.log(`Let's see what I've got in my secret pouch: 
3   ${secretPouch}`);
4 // => Let's see what I've got in my secret pouch: undefined

But beware, because if there is a missing property in the object graph on the way to the specific property you want, the destructuring will result in a SyntaxError:

1 let {centralPocket: {superSecretPouch}} = bag;
2 // => SyntaxError: 
3 //    Cannot read property 'superSecretPouch' of undefined

In this previous example the centralPocket property doesn’t exist in the bag object. This means that the JavaScript runtime cannot traverse the object to get to the superSecretPouch and thus you get the SyntaxError as a result.

And now that we’re talking about errors and problems, let’s take a look at something that you cannot do with destructuring, something that has bitten me repeatedly: Extracting a property into an existing variable. No, this here won’t work:

1 // let pouch = {coins: 10};
2 let money = 0;
3 {money} = pouch;

Yes, I know… But don’t be sad. The happy news is that you can also use all you’ve learned thus far with arrays! Wiii! Let’s destructure!

Destructuring Arrays

Destructuring arrays is just as easy as destructuring objects. Instead of using curly braces {} though, you’ll use the more familiar array square brackets []:

1 let [one, two, three] = ['goblin', 'ghoul', 'ghost', 'white walker'];
2 console.log(`one is ${one}, two is ${two}, three is ${three}`)
3 // => one is goblin, two is ghoul, three is ghost

In this example you see how we extract the first three elements of the array and place them in three distinct variables: one, two and three. The fourth element in the array, the scary white walker remains unreferenced by any variable.

You can also jump places within the array:

1 let [firstMonster, , , fourthMonster] = 
2   ['goblin', 'ghoul', 'ghost', 'white walker'];
3 console.log(`the first monster is ${firstMonster}, the fourth is 
4   ${fourthMonster}`)
5 // => one is goblin, two is ghoul, three is ghost

Destructuring arrays comes very handy when you want to get the first element of an array in a very readable and intuitive fashion:

1 let [first] = ['goblin', 'ghoul', 'ghost', 'white walker'];
2 console.log(`first is ${first}`)
3 // => first is goblin

Which you can combine with the rest operator like this:

1 let [first, ...rest] = ['goblin', 'ghoul', 'ghost', 'white walker'];
2 console.log(`first is ${first} and then go all the rest: ${rest}`)
3 // => first is goblin and then go all the rest ghoul, ghost, 
4 //    white walker

Unfortunately this trick doesn’t work for the last element of the array because the rest operator is greedy. It wants to extract all items in the array and therefore [...initialOnes, last] wouldn’t do the job.

You could be a super crafty fox and do the following:

1 let [last] = Array
2     .from(['goblin', 'ghoul', 'ghost', 'white walker'])
3     .reverse();
4 console.log(`last is ${last}`);
5 // => last is whiteWalker

Beautiful21! Another use case for array destructuring is to swap the values of two variables:

1 console.log(`first is ${first}, last is ${last}`);
2 // => first is goblin, last is white walker
3 
4 [first, last] = [last, first];
5 
6 console.log(`but wait! Now first is ${first}, last is ${last}`)
7 // => but wait! Now first is white walker, last is goblin

Finally, you can enjoy the versatile defaults when performing array destructuring as well:

1 let [aMonster, anotherMonster, yetAnotherMonster='cucumber'] 
2     = ['goblin', 'ghoul'];
3 console.log(`We've got a monster that is a ${aMonster}, 
4 another that is ${anotherMonster}, and yet another one 
5 that is a ${yetAnotherMonster}`);
6 // => We've got a monster that is a goblin, another that 
7 //    is ghoul, and yet another one that is a cucumber

Because we try to extract three elements from an array that only has two the yetAnotherMonster variable would get a value of undefined. The use of a default prevents that and ensures that the variable has a safe value of cucumber. Because nothing speaks of safety like a cucumber.

Destructuring Function Arguments

In Useful Function Patterns: Default Parameters you briefly saw how destructuring can be useful when used within the parameter list of a function.

 1 // With destructuring we can unpack the direction from
 2 // the incoming object and use it right away
 3 let randalf = {
 4     toString(){ return 'Randalf the Mighty'; },
 5     castIceCone(mana, {direction}){
 6         console.log(`${this} spends ${mana} mana 
 7             and casts a terrible ice cone ${direction}`);
 8     }
 9 };
10 
11 let options = { direction: 'towards Mordor'}
12 randalf.castIceCone(10, options);
13 // => Randalf the Mighty spends 10 mana and 
14 //    casts a terrible ice cone towards Mordor

The principle is the same but the destructuring process happens in a more indirect fashion. One piece of the destructuring is the object being passed as an argument to a method, in this case options:

1 randalf.castIceCone(10, options);

And the other bit is the destructuring syntax with the specific variables as part of the method signature {direction}:

1     castIceCone(mana, {direction}){

Just like normal destructuring it also works with arrays:

 1 function castMiniIceCone(mana, [target, ...others]){
 2     var caster = this || 'God almighty';
 3     console.log(`${caster} spends ${mana} mana 
 4 and casts a super teeny tiny ice cone that only reaches
 5 ${target} but misses ${others} because it is so tiny and cute`);
 6 }
 7 randalf.castMiniIceCone = castMiniIceCone;
 8 randalf.castMiniIceCone(10, ['giant', 'troll', 'death knight']);
 9 // => Randalf the Mighty spends 10 mana 
10 //    and casts a super teeny tiny ice cone that only reaches
11 //    giant but misses troll,death knight because it is so 
12 //    tiny and cute

And you can use any of the features you’ve learned in this section for both object and array parameters: defaults, nested properties, jumping over array items, etc.

Concluding

In this chapter we did a brief review of all the ES6 features that you’ve learned so far: let, const for block scoped variables, default parameters, rest parameters which work like C# params, shorthand syntax for object initializers, symbols, template literals, tags and the new string methods.

We also dived deeper into destructuring and learned how you can use this new feature to easily extract properties from objects and items from arrays. You discovered that you can use destructuring within the arguments of a function and how to combine destructuring and default values when the property you are trying to extract doesn’t exist within an object or array.

Exercises