ES6: Destructuring

This is the first post in a series I'll be doing about new ES6 features.  The goal is not to merely explain the concepts, but to also show "real-world" - or real enough - applications of the concepts so that you can understand why and when you might use them.  Hopefully you will be able to start recognizing scenarios or areas in your codebase that could benefit from these new features. After all, new language features should be used to not only help us write cleaner, more expressive code, they should also help us or even challenge us to think about the way we solve problems.

The first topic feature that will be covered is destructuring.

What?

Destructuring is a way of breaking down a data structure (de-structuring) into smaller parts.  ES6 adds this feature for use with arrays and objects.

Why?

Destructuring provides a cleaner, less verbose way of extracting values from objects and arrays.  Rather than having to write

let val = someObject.someProperty.maybeSomeNestedProperty

or in the case of an array, explicitly specify the index for the value you're trying to get, you can instead write

let { someProperty } = obj;

let [ firstVal ] = arr;

There are several other selling points, but before we dive into those, let's see some code.

How?

Objects

Let's start with objects.  The syntax for destructuring follows the same syntax as an object literal itself, a block statement.  Consider the code below:

let node = {
  type: "Test",
  name: "history",
  color: "red"
},
type = "Quiz",
name = 5;

Destructuring can be done in one of two ways:

// destructuring statement
let { color } = node;

// destructuring expression
({ type, name } = node); // override variable type with destructured value

The result of this is three variables - 'color', 'type', and 'name', all with the value of their respective property values.  It should be noted here that all of the three variable types - var, let, const - need an initializer (the object or array to the right of the assignment operator (=)).  As a side note, while var and let do not need to be initialized for non-destructured assigments, const always needs to be initialized, regardless of whether it's a destructured value or not.

If we print out these values, the result will be as below:

type = "Homework";
console.log(type); // Homework
console.log(name); // history
console.log(node.type); // Test

As you're hopefully already starting to see, the same syntax that is used to construct data can now be used to extract data.

Important to note here is that we actually aren't changing the object itself, which is why node.type still returns "Test" although we assigned the variable value to "Homework".  Destructuring doesn't modify the source, whether it is var, let or const.  Only the destructured variables (if they're var or let) are modified.

Assigning destructured variable to a different name

What if you don't want to use the property name as the variable name? You can change it like so:

let { type: localType, name: localName } = node;
console.log(type); // we can still use type
console.log(localType); // but we can also use localType

Side note: what happens if the object property or object name is wrong?  It will throw a ReferenceError:

console.log({ doesntExist } = node); // ReferenceError: doesntExist is not defined
console.log({ color } = tree); // ReferenceError: tree is not defined

Nested objects

Destructuring is also applicable to nested objects, like below:

let nested = {
  prop1: "ok",
  prop2: {
    location: {
      name: "LA",
      geo: {
        lat: '22.1',
        lng: '23.6'
      }
    }
  }
}

let { prop2: { location: { geo: renamedGeo }}} = nested;
console.log(renamedGeo.lat); // 22.1
renamedGeo.lat = 5; // changing the variable value for the heck of it
console.log(renamedGeo.lat); // 5

Arrays

Array destructuring is much like object destructuring, with the main difference being that you don't specify the index number.

const names = ["Sam", "John", "Liz"];

let [ , , thrName] = names;
console.log(thrName); // Liz

We can skip values in the array by leaving them blank. As you can see, thrName is an arbitrary name, in this case referring to the third position in the array.

Nested arrays

Just like with nested objects, so too can nested arrays be destructured:

let nested = ["Sam", ["John", "Liz"], "David"];
let [ one, [ two ], three] = nested;
console.log(one); // Sam
console.log(two); // John
console.log(three); // David

Mixed data structures

Lastly, it is possible to apply what we've learned above in order to destructure mixed data structures, like below:

let nested = {
  prop1: "ok",
  prop2: {
    location: {
      name: "LA",
      geo: {
        lat: '22.1',
        lng: '23.6'
      }
    }
  },
  arrayProp: [0, 1, 2]
}

let {
  prop2: { location },
  arrayProp: [ , second ]
} = nested;

console.log(location.name); // LA
console.log(second); // 1

Side notes

Sometimes you will see the object or array literal to the right of the destructuring statement or expression:

let [x, y] = ['a', 'b']; // x = 'a'; y = 'b'

With arrays, you can use the rest operator (another ES6 feature) to iterate through the values without having to explicitly call them out:

let arr = [1, 2, 3];
let [x,...y] = arr; // x=1, y[0]=2, y[1] = 3

Default values can be assigned if the object property or array value does not yet exist:

let node = {
  prop: 1
}
let { value = true } = node;

When?

If you're looking to convert some of your ES5 code to ES6, or just want to be aware of use cases for this new feature as you're developing a current or future application, the following will be patterns to keep an eye out for.

As mentioned in the beginning of this post, a big selling point for destructuring is its cleaner way of extracting data from a data structure, instead of having to write something verbose like let val = someObject.someProperty.maybeSomeNestedProperty or something repetitive like

let repeat = options.repeat,
save = options.save;

Another great use case is swapping values. Traditionally, developers have had to make use of a temp variable in order to swap values between variables, but now we can do this:

let a = 1,
b = 2;

[ a, b ] = [ b, a ];

console.log(a); // 2
console.log(b); // 1

Destructuring can be used with arrays and objects returned from a function, too:

function f() {
  return [1, 2];
}

let c, d;
[c, d] = f();
console.log(c); // 1
console.log(d); // 2

That's it for this week's post. It's sometimes difficult to read code interspersed with text, so I'll put the code on GitHub.

I have a whole backlog of topics for future posts, and I'm not sure if the next one will be on ES6 or not. If you find this post useful, would like more clarifications on the concept, or - most importantly - would like to better understand why you might use this feature, please comment down below.