ES6 In Depth: Destructuring

ES6 In Depth is a series on new features being added to the JavaScript programming language in the 6th Edition of the ECMAScript standard, ES6 for short.

Editor’s note: An earlier version of today’s post, by Firefox Developer Tools engineer Nick Fitzgerald, originally appeared on Nick’s blog as Destructuring Assignment in ES6.

What is destructuring assignment?

Destructuring assignment allows you to assign the properties of an array or object to variables using syntax that looks similar to array or object literals. This syntax can be extremely terse, while still exhibiting more clarity than the traditional property access.

Without destructuring assignment, you might access the first three items in an array like this:

var first = someArray[0];
var second = someArray[1];
var third = someArray[2];

With destructuring assignment, the equivalent code becomes more concise and readable:

var [first, second, third] = someArray;

SpiderMonkey (Firefox’s JavaScript engine) already has support for most of destructuring, but not quite all of it. Track SpiderMonkey’s destructuring (and general ES6) support in bug 694100.

Destructuring arrays and iterables

We already saw one example of destructuring assignment on an array above. The general form of the syntax is:

[ variable1, variable2, ..., variableN ] = array;

This will just assign variable1 through variableN to the corresponding item in the array. If you want to declare your variables at the same time, you can add a var, let, or const in front of the assignment:

var [ variable1, variable2, ..., variableN ] = array;
let [ variable1, variable2, ..., variableN ] = array;
const [ variable1, variable2, ..., variableN ] = array;

In fact, variable is a misnomer since you can nest patterns as deep as you would like:

var [foo, [[bar], baz]] = [1, [[2], 3]];
console.log(foo);
// 1
console.log(bar);
// 2
console.log(baz);
// 3
</pre>

Furthermore, you can skip over items in the array being destructured:

<pre>var [,,third] = ["foo", "bar", "baz"];
console.log(third);
// "baz"

And you can capture all trailing items in an array with a “rest” pattern:

var [head, ...tail] = [1, 2, 3, 4];
console.log(tail);
// [2, 3, 4]

When you access items in the array that are out of bounds or don’t exist, you get the same result you would by indexing: undefined.

console.log([][0]);
// undefined

var [missing] = [];
console.log(missing);
// undefined

Note that destructuring assignment with an array assignment pattern also works for any iterable:

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

var [first, second, third, fourth, fifth, sixth] = fibs();
console.log(sixth);
// 5

Destructuring objects

Destructuring on objects lets you bind variables to different properties of an object. You specify the property being bound, followed by the variable you are binding its value to.

var robotA = { name: "Bender" };
var robotB = { name: "Flexo" };

var { name: nameA } = robotA;
var { name: nameB } = robotB;

console.log(nameA);
// "Bender"
console.log(nameB);
// "Flexo"

There is a helpful syntactical shortcut for when the property and variable names are the same:

var { foo, bar } = { foo: "lorem", bar: "ipsum" };
console.log(foo);
// "lorem"
console.log(bar);
// "ipsum"

And just like destructuring on arrays, you can nest and combine further destructuring:

var complicatedObj = {
  arrayProp: [
    "Zapp",
    { second: "Brannigan" }
  ]
};

var { arrayProp: [first, { second }] } = complicatedObj;

console.log(first);
// "Zapp"
console.log(second);
// "Brannigan"

When you destructure on properties that are not defined, you get undefined:

var { missing } = {};
console.log(missing);
// undefined

One potential gotcha you should be aware of is when you are using destructuring on an object to assign variables, but not to declare them (when there is no let, const, or var):

{ blowUp } = { blowUp: 10 };
// Syntax error

This happens because the JavaScript grammar tells the engine to parse any statement starting with { as a block statement (for example, { console } is a valid block statement). The solution is to either wrap the whole expression in parentheses:

({ safe } = {});
// No errors

Destructuring values that are not an object, array, or iterable

When you try to use destructuring on null or undefined, you get a type error:

var {blowUp} = null;
// TypeError: null has no properties

However, you can destructure on other primitive types such as booleans, numbers, and strings, and get undefined:

var {wtf} = NaN;
console.log(wtf);
// undefined

This may come unexpected, but upon further examination the reason turns out to be simple. When using an object assignment pattern, the value being destructured is required to be coercible to an Object. Most types can be converted to an object, but null and undefined may not be converted. When using an array assignment pattern, the value must have an iterator.

Default values

You can also provide default values for when the property you are destructuring is not defined:

var [missing = true] = [];
console.log(missing);
// true

var { message: msg = "Something went wrong" } = {};
console.log(msg);
// "Something went wrong"

var { x = 3 } = {};
console.log(x);
// 3

(Editor’s note: This feature is currently implemented in Firefox only for the first two cases, not the third. See bug 932080.)

Practical applications of destructuring

Function parameter definitions

As developers, we can often expose more ergonomic APIs by accepting a single object with multiple properties as a parameter instead of forcing our API consumers to remember the order of many individual parameters. We can use destructuring to avoid repeating this single parameter object whenever we want to reference one of its properties:

function removeBreakpoint({ url, line, column }) {
  // ...
}

This is a simplified snippet of real world code from the Firefox DevTools JavaScript debugger (which is also implemented in JavaScript—yo dawg). We have found this pattern particularly pleasing.

Configuration object parameters

Expanding on the previous example, we can also give default values to the properties of the objects we are destructuring. This is particularly helpful when we have an object that is meant to provide configuration and many of the object’s properties already have sensible defaults. For example, jQuery’s ajax function takes a configuration object as its second parameter, and could be rewritten like this:

jQuery.ajax = function (url, {
  async = true,
  beforeSend = noop,
  cache = true,
  complete = noop,
  crossDomain = false,
  global = true,
  // ... more config
}) {
  // ... do stuff
};

This avoids repeating var foo = config.foo || theDefaultFoo; for each property of the configuration object.

(Editor’s note: Unfortunately, default values within object shorthand syntax still aren’t implemented in Firefox. I know, we’ve had several paragraphs to work on it since that earlier note. See bug 932080 for the latest updates.)

With the ES6 iteration protocol

ECMAScript 6 also defines an iteration protocol, which we talked about earlier in this series. When you iterate over Maps (an ES6 addition to the standard library), you get a series of [key, value] pairs. We can destructure this pair to get easy access to both the key and the value:

var map = new Map();
map.set(window, "the global");
map.set(document, "the document");

for (var [key, value] of map) {
  console.log(key + " is " + value);
}
// "[object Window] is the global"
// "[object HTMLDocument] is the document"

Iterate over only the keys:

for (var [key] of map) {
  // ...
}

Or iterate over only the values:

for (var [,value] of map) {
  // ...
}

Multiple return values

Although multiple return values aren’t baked into the language proper, they don’t need to be because you can return an array and destructure the result:

function returnMultipleValues() {
  return [1, 2];
}
var [foo, bar] = returnMultipleValues();

Alternatively, you can use an object as the container and name the returned values:

function returnMultipleValues() {
  return {
    foo: 1,
    bar: 2
  };
}
var { foo, bar } = returnMultipleValues();

Both of these patterns end up much better than holding onto the temporary container:

function returnMultipleValues() {
  return {
    foo: 1,
    bar: 2
  };
}
var temp = returnMultipleValues();
var foo = temp.foo;
var bar = temp.bar;

Or using continuation passing style:

function returnMultipleValues(k) {
  k(1, 2);
}
returnMultipleValues((foo, bar) => ...);

Importing names from a CommonJS module

Not using ES6 modules yet? Still using CommonJS modules? No problem! When importing some CommonJS module X, it is fairly common that module X exports more functions than you actually intend to use. With destructuring, you can be explicit about which parts of a given module you’d like to use and avoid cluttering your namespace:

const { SourceMapConsumer, SourceNode } = require("source-map");

(And if you do use ES6 modules, you know that a similar syntax is available in import declarations.)

Conclusion

So, as you can see, destructuring is useful in many individually small cases. At Mozilla we’ve had a lot of experience with it. Lars Hansen introduced JS destructuring in Opera ten years ago, and Brendan Eich added support to Firefox a bit later. It shipped in Firefox 2. So we know that destructuring sneaks into your everyday use of the language, quietly making your code a bit shorter and cleaner all over the place.

Five weeks ago, we said that ES6 would change the way you write JavaScript. It is this sort of feature we had particularly in mind: simple improvements that can be learned one at a time. Taken together, they will end up affecting every project you work on. Revolution by way of evolution.

Updating destructuring to comply with ES6 has been a team effort. Special thanks to Tooru Fujisawa (arai) and Arpad Borsos (Swatinem) for their excellent contributions.

Support for destructuring is under development for Chrome, and other browsers will undoubtedly add support in time. For now, you’ll need to use Babel or Traceur if you want to use destructuring on the Web.


Thanks again to Nick Fitzgerald for this week’s post.

Next week, we’ll cover a feature that is nothing more or less than a slightly shorter way to write something JS already has—something that has been one of the fundamental building blocks of the language all along. Will you care? Is slightly shorter syntax something you can get excited about? I confidently predict the answer is yes, but don’t take my word for it. Join us next week and find out, as we look at ES6 arrow functions in depth.

Jason Orendorff

ES6 In Depth editor

About Nick Fitzgerald

I like computing, bicycles, hiphop, books, and pen plotters. My pronouns are he/him.

More articles by Nick Fitzgerald…

About Jason Orendorff

More articles by Jason Orendorff…


12 comments

  1. Kyle Simpson

    This is not allowed anymore by ES6:

    “`js
    ({ safe }) = {};
    “`

    It has to be:

    “`js
    ({ safe } = {});
    “`

    May 28th, 2015 at 17:35

    1. Josh Strange

      Oddly enough they only show the right example on their website: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment

      May 28th, 2015 at 20:18

      1. Kyle Simpson

        That’s because I updated the MDN page awhile back. ;-)

        May 29th, 2015 at 12:17

    2. Nick Fitzgerald

      Woops! Updated. Thanks!

      May 29th, 2015 at 11:35

    3. Aesop Wolf

      Is there a canonical source where can I keep up to date with these kinds of changes?

      May 31st, 2015 at 00:30

  2. thelinuxlich

    Hope someday we can have a cool pattern matching feature :)

    May 28th, 2015 at 18:21

  3. Daniel

    Feels a bit like Erlang’s pattern-matching. Neat.

    May 28th, 2015 at 20:15

  4. Giacomo Tagliabue

    Adding to the transpilers list, you can also use Typescript 1.5 (still in beta) to have destructuring support :)

    issue [here](https://github.com/Microsoft/TypeScript/issues/240)

    May 29th, 2015 at 00:47

  5. Jason

    Will this be something that can be used in the other browsers as well? I just see this working for Firefox?

    June 1st, 2015 at 06:10

    1. Nick Fitzgerald

      Yup, but it might be a little while until support exists across all the browsers. See the end of the article for your options for working around this, eg Traceur, Babel, TypeScript, etc.

      June 1st, 2015 at 11:27

  6. Simon Speich

    var first = someArray[0],
    second = someArray[1],
    third = someArray[2];

    would kind of align nicer with the comparison to

    var [first, second, third] = someArray;

    June 3rd, 2015 at 05:47

  7. Jason

    Destructuring would be 1000% easier to read if it just used reverse fat arrow (<=) instead of equals.

    let { x, y } <= somObj;

    I know EXACTLY what that's doing, intuitively, as opposed to

    let { x, y } = somObj;

    which I look at and go "wtf"

    June 17th, 2015 at 17:29

Comments are closed for this article.