This blog post is outdated. Please read the following two chapters in “Exploring ES6”:
ECMAScript 6 (ES6) supports destructuring, a convenient way to extract values from data stored in (possibly nested) objects and arrays. This blog post describes how it works and gives examples of its usefulness. Additionally, parameter handling receives a significant upgrade in ES6: it becomes similar to and supports destructuring, which is why it is explained here, too.
In locations that receive data (such as the left-hand side of an assignment), destructuring lets you use patterns to extract parts of that data. In the following example, we use destructuring in a variable declaration (line (A)). It declares the variables and and assigns them the values and .
Destructuring can be used in the following locations. Each time, is set to .
And it has operations for extracting data:
Note that we are using the same syntax that we have used for constructing.
There is nicer syntax for constructing – an object literal:
Destructuring in ECMAScript 6 enables the same syntax for extracting data, where it is called an object pattern:
Just as the object literal lets us create multiple properties at the same time, the object pattern lets us extract multiple properties at the same time.
You can also destructure arrays via patterns:
- Destructuring source: the data to be destructured. For example, the right-hand side of a destructuring assignment.
- Destructuring target: the pattern used for destructuring. For example, the left-hand side of a destructuring assignment.
Being selective with parts #
If you destructure an object, you are free to mention only those properties that you are interested in:
If you destructure an array, you can choose to only extract a prefix:
If a part has no match #
You can nest patterns arbitrarily deeply:
How do patterns access the innards of values? #
In an assignment , how does the acess what’s inside ?
Object patterns coerce values to objects #
The object pattern coerces destructuring sources to objects before accessing properties. That means that it works with primitive values:
Failing to object-destructure a value #
The coercion to object is not performed via , but via the internal operation . never fails:
throws a if it encounters or . Therefore, the following destructurings fail, even before destructuring accesses any properties:
As a consequence, you can use the empty object pattern to check whether a value is coercible to an object. As we have seen, only and aren’t:
Array patterns work with iterables #
Array destructuring uses an iterator to get to the elements of a source. Therefore, you can array-destructure any value that is iterable. Let’s look at examples of iterable values.
Strings are iterable:
You can’t access the elements of a set via indices, but you can do so via an iterator. Therefore, array destructuring works for sets:
The iterator always returns elements in the order in which they were inserted, which is why the result of the previous destructuring is always the same.
Infinite sequences. Destructuring also works for iterators over infinite sequences. The generator function returns an iterator that yields 0, 1, 2, etc.
The following destructuring extracts the first three elements of that infinite sequence.
Failing to array-destructure a value #
A value is iterable if it has a method whose key is that returns an object. Array-destructuring throws a if the value to be destructured isn’t iterable:
The is thrown even before accessing elements of the iterable, which means that you can use the empty array pattern to check whether a value is iterable:
Default values #
Default values are a feature of patterns:
- Each part of a pattern can optionally specify a default value.
- If the part has no match in the source, destructuring continues with the default value (if one exists) or .
Let’s look at an example. In the following destructuring, the element at index 0 has no match on the right-hand side. Therefore, destructuring continues by matching against 3, which leads to being set to 3.
You can also use default values in object patterns:
Default values are also used if a part does have a match and that match is :
The rationale for this behavior is explained later, in the section on parameter default values.
Default values are computed on demand #
The default values themselves are only computed when they are needed. That is, this destructuring:
is equivalent to:
You can observe that if you use :
In the second destructuring, the default value is not needed and is not called.
Default values can refer to other variables in the pattern #
A default value can refer to any variable, including another variable in the same pattern:
However, order matters: the variables and are declared from left to right and produce a if they are accessed before their declaration.
Default values for patterns #
So far we have only seen default values for variables, but you can also associate them with patterns:
What does this mean? Recall the rule for default values:
If the part has no match in the source, destructuring continues with the default value […].
The element at index 0 has no match, which is why destructuring continues with:
You can more easily see why things work this way if you replace the pattern with the variable :
More complex default values. Let’s further explore default values for patterns. In the following example, we assign a value to via the default value :
Because the array element at index 0 has no match on the right-hand side, destructuring continues as follows and is set to 123.
However, is not assigned a value in this manner if the right-hand side has an element at index 0, because then the default value isn’t triggered.
In this case, destructuring continues with:
Thus, if you want to be 123 if either the object or the property is missing, you need to specify a default value for itself:
Here, destructuring continues as follows, independently of whether the right-hand side is or .
More object destructuring features #
Property value shorthands #
Property value shorthands are a feature of object literals: If the value of a property is provided via a variable whose name is the same as the key, you can omit the key. This works for destructuring, too:
This declaration is equivalent to:
You can also combine property value shorthands with default values:
Computed property keys #
Computed property keys are another object literal feature that also works for destructuring: You can specify the key of a property via an expression, if you put it in square brackets:
Computed property keys allow you to destructure properties whose keys are symbols :
More array destructuring features #
Elision lets you use the syntax of array “holes” to skip elements during destructuring:
Rest operator #
The rest operator () lets you extract the remaining elements of an array into an array. You can only use the operator as the last part inside an array pattern:
[Note: This operator extracts data. The same syntax () is used by the spread operator, which constructs and is explained later.]
If the operator can’t find any elements, it matches its operand against the empty array. That is, it never produces or . For example:
The operand of the rest operator doesn’t have to be a variable, you can use patterns, too:
The rest operator triggers the following destructuring:
You can assign to more than just variables #
If you assign via destructuring, each variable part can be everything that is allowed on the left-hand side of a normal assignment, including a reference to a property () and a reference to an array element ().
You can also assign to object properties and array elements via the rest operator ():
If you declare variables via destructuring then you must use simple identifiers, you can’t refer to object properties and array elements.
Pitfalls of destructuring #
There are two things to be mindful of when using destructuring.
Don’t start a statement with a curly brace #
Because code blocks begin with a curly brace, statements must not begin with one. This is unfortunate when using object destructuring in an assignment:
The work-around is to either put the pattern in parentheses or the complete expression:
You can’t mix declaring and assigning to existing variables #
Within a destructuring variable declaration, every variable in the source is declared. In the following example, we are trying to declare the variable and refer to the existing variable , which doesn’t work.
The fix is to use a destructuring assignment and to declare beforehand:
Examples of destructuring #
Let’s start with a few smaller examples.
The loop supports destructuring:
You can use destructuring to swap values. That is something that engines could optimize, so that no array would be created.
You can use destructuring to split an array:
Destructuring return values #
returns if the regular expression doesn’t match. Unfortunately, you can’t handle via default values, which is why you must use the Or operator () in this case:
Multiple return values #
To see the usefulness of multiple return values, let’s implement a function that searches for the first element in the array for which the function returns . The question is: what should that function return? Sometimes one is interested in the element itself, sometimes in its index, sometimes in both. The following implementation does both.
In line (A), the array method returns an iterable over pairs. We destructure one pair per iteration. In line (B), we use property value shorthands to return the object .
In the following example, we use several ECMAScript features to write more concise code: An arrow functions helps us with defining the callback, destructuring and property value shorthands help us with handling the return value.
Due to and also referring to property keys, the order in which we mention them doesn’t matter:
We have successfully handled the case of needing both index and element. What if we are only interested in one of them? It turns out that, thanks to ECMAScript 6, our implementation can take care of that, too. And the syntactic overhead compared to functions that support only elements or only indices is minimal.
Each time, we only extract the value of the one property that we need.
Parameter handling #
Parameter handling has been significantly upgraded in ECMAScript 6. It now supports parameter default values, rest parameters (varags) and destructuring. The new way of handling parameters is equivalent to destructuring the actual parameters via the formal parameters. That is, the following function call:
is equivalent to:
Let’s look at specific features next.
Parameter default values #
ECMAScript 6 lets you specify default values for parameters:
Omitting the second parameter triggers the default value:
Watch out – triggers the default value, too:
The default value is computed on demand, only when it is actually needed:
Why does trigger default values? #
It isn’t immediately obvious why should be interpreted as a missing parameter or a missing part of an object or array. The rationale for doing so is that it enables you to delegate the definition of default values. Let’s look at two examples.
In the first example (source: Rick Waldron’s TC39 meeting notes from 2012-07-24), we don’t have to define a default value in , we can delegate that task to .
In the second example, doesn’t have to define a default for , it can delegate that task to :
Default values further entrench the role of as indicating that something doesn’t exist, versus indicating emptiness.
Referring to other variables in default values #
Within a parameter default value, you can refer to any variable, including other parameters:
However, order matters: parameters are declared from left to right and within a default value, you get a if you access a parameter that hasn’t been declared, yet.
Default values exist in their own scope, which is between the “outer” scope surrounding the function and the “inner” scope of the function body. Therefore, you can’t access inner variables from the default values:
If there were no outer in the previous example, the default value would produce a .
Rest parameters #
Putting the rest operator () in front of the last formal parameter means that it will receive all remaining actual parameters in an array.
If there are no remaining parameters, the rest parameter will be set to the empty array:
No more ! #
One interesting feature of is that you can have normal parameters and an array of all parameters at the same time:
You can avoid in such cases if you combine a rest parameter with array destructuring. The resulting code is longer, but more explicit:
Note that is iterable in ECMAScript 6, which means that you can use and the spread operator:
Simulating named parameters #
When calling a function (or method) in a programming language, you must map the actual parameters (specified by the caller) to the formal parameters (of a function definition). There are two common ways to do so:
Positional parameters are mapped by position. The first actual parameter is mapped to the first formal parameter, the second actual to the second formal, and so on.
Named parameters use names (labels) to perform the mapping. Names are associated with formal parameters in a function definition and label actual parameters in a function call. It does not matter in which order named parameters appear, as long as they are correctly labeled.
Named Parameters as Descriptions #
As soon as a function has more than one parameter, you might get confused about what each parameter is used for. For example, let’s say you have a function, , that returns entries from a database. Given the function call:
what do these two numbers mean? Python supports named parameters, and they make it easy to figure out what is going on:
Optional Named Parameters #
Optional positional parameters work well only if they are omitted at the end. Anywhere else, you have to insert placeholders such as so that the remaining parameters have correct positions.
With optional named parameters, that is not an issue. You can easily omit any of them. Here are some examples:
The function receives an object with the properties , , and . You can omit any of them:
In ECMAScript 5, you’d implement as follows:
In ECMAScript 6, you can use destructuring, which looks like this:
If you call with zero arguments, the destructuring fails, because you can’t match an object pattern against . That can be fixed via a default value. In the following code, the object pattern is matched against if there isn’t at least one argument.
You can also combine positional parameters with named parameters. It is customary for the latter to come last:
Pitfall: destructuring a single arrow function parameter #
Arrow functions have a special single-parameter version where no parentheses are needed:
The single-parameter version does not support destructuring:
Examples of parameter handling #
forEach() and destructuring #
You will probably mostly use the loop in ECMAScript 6, but the array method also profits from destructuring. Or rather, its callback does.
First example: destructuring the arrays in an array.
Second example: destructuring the objects in an array.
Transforming maps #
An ECMAScript 6 Map doesn’t have a method (like arrays). Therefore, one has to:
- Convert it to an array of pairs.
- the array.
- Convert the result back to a map.
This looks as follows.
Handling an array returned via a Promise #
The tool method works as follows:
- Input: an array of Promises.
- Output: a Promise that resolves to an array as soon as the last input Promise is resolved. The array contains the resolutions of the input Promises.
Destructuring helps with handling the array that the result of resolves to:
is a Promise-based version of . It is part of the Fetch standard.
Required parameters #
In ECMAScript 5, you have a few options for ensuring that a required parameter has been provided, which are all quite clumsy:
In ECMAScript 6, you can (ab)use default parameter values to achieve more concise code (credit: idea by Allen Wirfs-Brock):
Enforcing a maximum arity #
This section presents three approaches to enforcing a maximum arity. The running example is a function whose maximum arity is 2 – if a caller provides more than 2 parameters, an error should be thrown.
The first approach collects all actual parameters in the formal rest parameter and checks its length.
The second approach relies on unwanted actual parameters appearing in the formal rest parameter .
The third approach uses a sentinel value that is gone if there is a third parameter. One caveat is that the default value is also triggered if there is a third parameter whose value is .
Sadly, each one of these approaches introduces significant visual and conceptual clutter. I’m tempted to recommend checking , but I also want to go away.
The spread operator () #
The spread operator () is the opposite of the rest operator: Where the rest operator extracts arrays, the spread operator turns the elements of an array into the arguments of a function call or into elements of another array.
Spreading into function and method calls #
is a good example for demonstrating how the spread operator works in method calls. returns the argument whose value is greatest. It accepts an arbitrary number of arguments, but can’t be applied to arrays. The spread operator fixes that:
In contrast to the rest operator, you can use the spread operator anywhere in a sequence of parts:
Spreading into constructors #
In addition to function and method calls, the spread operator also works for constructor calls:
That is something that is difficult to achieve in ECMAScript 5.
Spreading into arrays #
The spread operator can also be used inside arrays:
That gives you a convenient way to concatenate arrays:
Converting iterable or array-like objects to arrays #
The spread operator lets you convert any iterable object to an array:
Let’s convert a set to an array:
Your own iterable objects can be converted to arrays in the same manner:
Note that, just like the loop, the spread operator only works for iterable objects. Most important objects are iterable: arrays, maps, sets and . Most DOM data structures will also eventually be iterable.
Should you ever encounter something that is not iterable, but array-like (indexed elements plus a property ), you can use to convert it to an array:
Further reading: #
There’s one last thing we need to know about destructuring objects, and that is the ability to set defaults. This one’s a little bit confusing, so bear with me here and we’re going to circle back for another example later on in a couple of videos over at ES6.io.
When you destructure an object, what happens if that value isn’t there?
What is width? It’s because we create the variable, but it’s not able to be set to anything.
With destructuring we can set defaults, or fallback values so that if an item is not in the object (or Array, Map, or Set) it will fall back to what you have set at the default.
This syntax is a little hard to read:
Now if the or properties don’t exist on our object, they fallback to and respectively.
Careful: null and undefined
One thing to note here is that this isn’t 100% the same as this old trick used to fallback when is not set:
Why? Because ES6 destructuring default values only kick in if the value is undefined. null, false and 0 are all still values!
Combining with Destructuring Renaming
In my last post we learned that we can destrucutre and rename varaibles at the same time with something like this:
We can also set defaults in the same go. Hold onto your head because this syntax is going to get funky!
Woah – let’s step through that one!
- First we create a new const var called .
- Next we look for . If there was a property, it would be put into the variable.
- There isn’t a property on our object, so we fall back to the default of .
Cool! Make sure to check out ES6.io for more like this!