Going even deeper into ES6 destructuring now, this post shows how to get maximum awesomeness by using destructuing on deeply nested objects, functions, generators, and even regExes!

Destructuring Arrays and Objects together

In my previous post on how to actually use ES6 destructuring, I showed separate examples for array destructuring and object destructuring.

But what if you wanted to destructure both at the same time? Does that even make sense? Turns out it does:

Extract object property and array from a complex object


   const avengers = {
      operation: 'Assemble',
      members: [
         { ironMan: 'Tony Stark' },
         { captainAmerica: 'Steve Rogers' },
         { blackWidow: 'Natasha Romanoff' }
      ]};

   const { operation, members } = avengers;

   // operation = 'Assemble'
   // members = [{ ironMan: 'Tony Stark' },
   //    { captainAmerica: 'Steve Rogers' },
   //    { blackWidow: 'Natasha Romanoff' }]

   // Output members:
   members;

Extract object property and array item from a complex object


   const avengers = {
      operation: 'Assemble',
      members: [
         { ironMan: 'Tony Stark' },
         { captainAmerica: 'Steve Rogers' },
         { blackWidow: 'Natasha Romanoff' }
      ]};

   const { operation, members:[, cap] } = avengers;

   // operation = 'Assemble'
   // cap = { captainAmerica: 'Steve Rogers' }

   // Output cap:
   cap;

This last example might need explaining. What exactly is this doing?

const { operation, members:[, cap] } = avengers;

Breaking it down, we can ignore the operation variable, as that’s extracting the operation property, and focus instead on the members:... part of the destructuring object pattern.

The first thing to note is members, which is used to identify the object property we’re interested in – in this case, the members property of the avengers object.

But this property’s value happens to be an array, so to pluck an item from this array, we need an array pattern inside the object pattern. That’s what [, cap] is doing – it’s an array pattern for extracting the item with the value Captain America from the members array.

To make this easier to understand, let’s break it down further. Rather than burying the members array inside a complex object, let’s break it out into its own declaration:

   const members = [
         { ironMan: 'Tony Stark' },
         { captainAmerica: 'Steve Rogers' },
         { blackWidow: 'Natasha Romanoff' }
      ];

If you wanted to destructure this to extract captainAmerica, you’d write:

   const [, // ignore ironMan
          cap // assign the second item to the newly-declared variable 'cap'
   ] = members;
   // cap = { captainAmerica: 'Steve Rogers' } 

Or, more succinctly:

   const [, cap] = members;
   // cap = { captainAmerica: 'Steve Rogers' } 

Destructuring complex objects can be a bit of a brain-bender, but only if you try to destructure everything yourself all at once. If you’re not sure what’s going on, just manually deconstruct the complex object into its component parts first and then try to destructure each component part individually.

Your brain will thank you for it.

Destructuring Functions

Using destructuring in function parameters

If you have a function that requires multiple parameters (which you really shouldn’t, but let’s assume you do!), it can be error prone passing those parameters to the function. For example:


// Note - ES5 code:
function createAvenger(avengerName, realName, height, weight, age) {
    return {
        avengerName: avengerName,
        realName: realName,
        height: height,
        weight: weight,
        age: age
    }
};

var ironMan = createAvenger('Iron Man', 'Tony Stark', 1.85, 102, 46);
// ironMan = { 
//   avengerName: 'Iron Man',
//   realName: 'Tony Stark'
//   height: 1.85,
//   weight: 102,
//   age: 46;
// }

// Output ironMan:
ironMan;

Without seeing the function definition of createAvenger, it’s not obvious what the parameters refer to (particularly the last three numbers). Worse, without knowing what they are, it’s easy to mix them up, as there’s no context to the numbers – they’re just numbers without any meaning – yet the ordering of the parameters is critical.

One way around this in ES5 is to bundle the parameters up into a single Configuration Object. For example:


  // Note - ES5 code:
  function createAvenger (options) {
    return {
      avengerName: options.avengerName,
      realName: options.realName,
      height: options.height,
      weight: options.weight,
      age: options.age
    }
  };
  
  var ironManOpts = {
     avengerName: 'Iron Man',
     realName: 'Tony Stark',
     height: 1.85,
     weight: 102,
     age: 46
  }

  var ironMan = createAvenger(ironManOpts);
  // ironMan = { 
  //   avengerName: 'Iron Man',
  //   realName: 'Tony Stark',
  //   height: 1.85,
  //   weight: 102,
  //   age: 46
  // }

  // Output ironMan:
  ironMan;


This is safer than before, as now parameter ordering doesn’t matter. Because we’re working with object properties, not individual arguments, each parameter is now effectively given a meaningful context in the form of its associated property key. It’s therefore obvious what { height: 1.85 } is referring to, whereas the argument 1.85 is meaningless on its own.

But it’s still a bit of a faff to write, as we have to remember to use the options parameter name throughout the createAvengers function, which clutters up the code. Equally, although the options object itself gives us the context we need to understand the parameter values we’re supplying to the function, we have no such joy in the function declaration itself. All we have is a single, meaningless options object, forcing us to document the function extensively if we want other users to understand what properties options should contain.

So although we’ve given the function caller more meaningful context, we’ve done it at the expense of losing that meaning in the function declaration.

With destructuring, though, all that changes, and we get the best of both worlds:


  function createAvenger ({ avengerName, realName, height, weight, age }) {
    return {
      avengerName,
      realName,
      height,
      weight,
      age
    }
  };
  
  const ironManOpts = {
     avengerName: 'Iron Man',
     realName: 'Tony Stark',
     height: 1.85,
     weight: 102,
     age: 46
  }

  const ironMan = createAvenger(ironManOpts);
  // ironMan = { 
  //   avengerName: 'Iron Man',
  //   realName: 'Tony Stark',
  //   height: 1.85,
  //   weight: 102,
  //   age: 46
  // }

  // Output ironMan:
  ironMan;


Using destructuring in the function definition, we’re still effectively using an options object, but it’s no longer defined explicitly as a named object; rather, it’s an anonymous object that’s automatically destructured into its component properties.

The benefits this brings include:

  • meaningful parameters whose ordering doesn’t matter
  • an explicit, self-documenting, parameter list in the form of the object properties that are part of the function definition
  • less boilerplate code, as an options object no longer needs to be referred to within the function body

This pattern is so useful, you’ll see it appear time after time in any framework or library that uses ES6 (such as React).

Handling Missing Parameters

Providing default values to a function’s Configuration Object

With ES5, our options object needed a fair amount of boilerplate to make it safe to use (i.e. tests to ensure it existed before attempting to access its properties), and to provide it with default values, should any of the properties not exist. Using the example above, a fully safe, default-providing function using an options object would look as follows:



  // Note - ES5 code:
  function createAvenger (options) {
    options = options || {};
    
    return {
      avengerName: options.avengerName || 'unknown',
      realName: options.realName || 'unknown',
      height: options.height || 0,
      weight: options.width || 0,
      age: options.age || 0
    }
  };
  
  var ironMan = createAvenger();

  // ironMan = { 
  //   avengerName: 'unknown',
  //   realName: 'unknown',
  //   height: 0,
  //   weight: 0,
  //   age: 0;
  // }

  // Output ironMan:
  ironMan;


That’s a lot of boilerplate in the createAvenger function. But with ES6, we can simply do this:



  // Note - ES6 code:
  function createAvenger ({ avengerName = 'unknown', realName = 'unknown', height = 0, weight = 0, age = 0 } = {}) {
    return {
      avengerName,
      realName,
      height,
      weight,
      age
    }
  };
  
  const ironMan = createAvenger();

  // ironMan = { 
  //   avengerName: 'unknown',
  //   realName: 'unknown',
  //   height: 0,
  //   weight: 0,
  //   age: 0;
  // }

  // Output ironMan:
  ironMan;


Much neater. By adding the defaults to the object pattern in the function definition, we can be assured that all the parameter values are safe to use in the function body, with ready-made defaults already provided if needed. The function body can then get on with providing the algorithm, with all boilerplate removed.

Enforcing Required Parameters

An alternative to assigning default values to function parameters is to throw an error if any required value is not present. This is obviously particularly important for security-critical code.

Destructuring can help us here as well, again with default values. In this case, however, rather than assigning a default value to a parameter, we assign it a function instead that checks for the value’s existence:



  function requiredParam(param) {
    throw new Error(`Error: Required parameter ${param} is missing`);
  }

  function createAvenger ({ avengerName = requiredParam('avengerName'), realName = 'unknown' } = {}) {
    return {
      avengerName,
      realName
    }
  };
  
  const ironMan = createAvenger();
  // Error: Required parameter avengerName is missing

  // Output ironMan:
  ironMan;


In this example, if avengerName has no value, it’s assigned the value returned from requiredParam(). This function should never be called, though, as it’s only called if the required parameter doesn’t exist. Accordingly, all the function does is throw an exception, passing the name of the missing required parameter in the exception that it throws.

Handling multiple return values

Extract a single property from an object returned from a function

If a function needs to return multiple values, it can do so by returning them as either an object or an array in both ES5 and ES6. But with ES5, it can get messy if you only need one of the returned values. For example:


// Note - ES5 code: 
function findAvenger(avengers, avengerName) {
    for (var index = 0; index < avengers.length; index++) {
        if (avengers[index].avengerName === avengerName) {
            return avengers[index];
        }
    }
}

var avengers = [{
    avengerName: 'ironMan',
    realName: 'Tony Stark'
}, {
    avengerName: 'captainAmerica',
    realName: 'Steve Rogers'
}, {
    avengerName: 'blackWidow',
    realName: 'Natasha Romanoff'
}];

var avenger = findAvenger(avengers, 'ironMan');

// If you only need the returned avengerName:
var avengerName = avenger.name;
//avengerName = 'ironMan'

// If you only need the returned realName:
var realName = avenger.realName;
//realName = 'Tony Stark'

// Output realName:
realName;

With ES6, we can destructure the returned object:


// Note - ES6 code: 
function findAvenger(avengers, avengerName) {
    for (var index = 0; index < avengers.length; index++) {
        if (avengers[index].avengerName === avengerName) {
            return avengers[index];
        }
    }
}

var avengers = [{
    avengerName: 'ironMan',
    realName: 'Tony Stark'
}, {
    avengerName: 'captainAmerica',
    realName: 'Steve Rogers'
}, {
    avengerName: 'blackWidow',
    realName: 'Natasha Romanoff'
}];


// If you only need the returned avengerName:
const { name } = findAvenger(avengers, 'ironMan');
//avengerName = 'ironMan'

// If you only need the returned realName:
const { realName } = findAvenger(avengers, 'ironMan');
//realName = 'Tony Stark'

// Output realName:
realName;

You're extracting just the value you need from the multiple values returned from the function.

Extract a single property from an array returned from a function

Of course, the same process works with arrays, too:



// Note - ES6 code: 
function getAvenger() {
    return [{
        avengersName: 'ironMan',
        realName: 'Tony Stark'
    }, {
        avengersName: 'captainAmerica',
        realName: 'Steve Rogers'
    }, {
        avengersName: 'blackWidow',
        realName: 'Natasha Romanoff'
    }];
}

// If you only need ironMan:
const [ironMan] = getAvenger();
// ironMan = { avengersName: 'ironMan': realName: 'Tony Stark' }

// If you only need captainAmerica:
const [, captainAmerica] = getAvenger();
// captainAmerica = { avengersName: 'captainAmerica', realName: 'Steve Rogers' }

// If you only need blackWidow:
const [, , blackWidow] = getAvenger();
// blackWidow = { avengersName: 'blackWidow', realName: 'Natasha Romanoff' }

// Output blackWidow:
blackWidow;

Destructuring Iterators

Destructuring Generators

A generator is an iterator factory function that lets you iterate through a potentially infinite collection of items without blocking other program code.

For example (and apologies for the lack of Avengers in this one!), the following shows a generator function for generating fibonacci numbers:



  function* fibonacci() {
    let a = 0;
    let b = 1;

    while (true) { 
      yield a;
      [a, b] = [b, a + b];
    }
  }

  const fib = fibonacci();

  const first   = fib.next().value; // 0
  const second  = fib.next().value; // 1
  const third   = fib.next().value; // 1
  const fourth  = fib.next().value; // 2
  const fifth   = fib.next().value; // 3
  const sixth   = fib.next().value; // 5
  const seventh = fib.next().value; // 8

  // Output seventh:
  seventh;

Notice how it's you who controls when the next value in the sequence is generated, by calling the iterator's built-in next() function.

This isn't exactly DRY, though, so let's try destructuring it:



  function* fibonacci() {
    let a = 0;
    let b = 1;
  
    while (true) {
      yield a;
      [a, b] = [b, a + b];
    }
  }

  const fib = fibonacci();

  const [first, second, third, fourth, fifth, sixth, seventh] = fib;

  // Output seventh:
  seventh;

Much better! Want only the 10th value?

...
const [, , , , , , , , , tenth] = fib;

OK, so counting commas isn't quite so useful, but if you ever need to use Generators, knowing you can extract them to a destructured array so easily makes them much easier to use.

Destructuring Maps

In ES5, objects were frequently used as a simple data type (sometimes called a hash) to store a set of key:value pairs. However, using objects like this is fraught with danger, as they come with so much baggage to make them, well, objects, all of which can get in the way of your keys and values.

ES6 solves this with the new Map object, which is an explicit, first-class object that's built-in to the language itself. It removes all of the issues of using an object as a map, and has many useful methods for manipulating its set of key:value pairs that a traditional object doesn't have.

Better still, you can use destructuring with it to get at your keys and your values really easily.

Iterating over key, value pairs with Maps



const map = new Map();
let output = [];

map.set('ironMan', 'Tony Stark');
map.set('captainAmerica', 'Steve Rogers');
map.set('blackWidow', 'Natasha Romanoff');

for (const [key, value] of map) {
    output.push(key + " is " + value);
}

// Output:
// ironMan is Tony Stark
// captainAmerica is Steve Rogers
// blackWidow is Natasha Romanoff

output;


Iterate over only the keys



  const map = new Map();
  let output = [];

  map.set('ironMan', 'Tony Stark');
  map.set('captainAmerica', 'Steve Rogers');
  map.set('blackWidow', 'Natasha Romanoff');

  for (const [key] of map) {
    output.push('AvengerName is ' + key);
  }

  // Output:
  // AvengerName is ironMan
  // AvengerName is captainAmerica
  // AvengerName is blackWidow

  output;

Iterate over only the values



  const map = new Map();
  let output = [];

  map.set('ironMan', 'Tony Stark');
  map.set('captainAmerica', 'Steve Rogers');
  map.set('blackWidow', 'Natasha Romanoff');

  for (const [, value] of map) {
    output.push('RealName is ' + value);
  }

  // Output:
  // RealName is Tony Stark
  // RealName is Steve Rogers
  // RealName is Natashe Romanoff

  output;


Destructuring Regular Expressions

Destructuring works beautifully when a function returns an object or array. Know what returns an array? JavaScript's RegEx.exec() function! Just feed it a regular expression, and it'll return an array, which you can destructure into nicely-named variables, like this:



  const email = 'user.name@address.com';

  const [originalString, username, address, tld] 
    = /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/.exec(email);

  // originalString = user.name@address.com
  // username = user.name
  // address = address
  // tld = com

  // Output username:
  username;


Summary

We've now moved away from destructuring techniques, and more into the realm of real-world recipes for doing actual things. So in the next post, we'll finish this series with a look at just how you can use destructuring in your actual real-world code, and really max out on our destructuring awesomeness.

All posts in this series on destructuring

Other Useful Links

Comments
  • Harvey Specter
    Posted at 6:52 am Jul 22, 2016
    Christian Hagendorn
    Reply
    Author

    Thanks, really good article!

  • Harvey Specter
    Posted at 10:33 am Jul 22, 2016
    Ivan
    Reply
    Author

    thank you for teaching me something new! Some advance staff. Good article!

  • Harvey Specter
    Posted at 10:33 pm Jul 22, 2016
    Martijn
    Reply
    Author

    “Iterating over key, value pairs with Maps”
    So what’s the point of those “SyntaxError: missing = in const declaration” errors? The text readsas if youĂ© showing how it’s done, but then in the code you’re showing us it can’t be done? What’s up with that?

    • Harvey Specter
      Posted at 6:47 pm Jul 23, 2016
      Mike Evans
      Reply
      Author

      Not sure what you mean, Martijn. I’m not seeing the errors you mention. What browser are you using?

  • Harvey Specter
    Posted at 4:24 am Jul 23, 2016
    Arsen Melikyan
    Reply
    Author

    Thanks a lot for an awesome article!
    Notice how it’s you who controls when the next value in the sequence is generated, by calling the generator’s built-in next() function.
    I think you mean here iterator’s built-in next() function.

    • Harvey Specter
      Posted at 6:45 pm Jul 23, 2016
      Mike Evans
      Reply
      Author

      Good spot, thanks Arsen. I’ve updated it.

  • Harvey Specter
    Posted at 5:18 pm Jul 25, 2016
    Paolo Caminiti
    Reply
    Author

    Great article! Destructuring a generator to a spread has become my new favourite way to crash a tab!

  • Harvey Specter
    Posted at 7:00 pm Jan 30, 2017
    vladimir
    Reply
    Author

    Hi. Great artcle. I learned something new today.
    P.S It seems that the link to Part 3 below the article is broken.

    • Harvey Specter
      Posted at 12:18 pm Feb 4, 2017
      Mike Evans
      Reply
      Author

      Thanks Vladimir. Fixed.

  • Leave a Reply

    You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>