frontend
Destructuring Arrays & Objects: JavaScript ES6 Feature Series (Pt 10)
For concise variable syntax, curly braces have never been more critical
Introduction
The inspiration for these pieces is simple: JavaScript, and especially some of the new ES6+ releases, can be more than a little perplexing at first glance to many developers. To put it bluntly: what they once thought they understood becomes a completely new beast when another layer of syntactic sugar, designed to make our lives easier, is added.
That being said, according to Wikipedia, JavaScript still powers almost 95% of the 10 million most popular webpages today.
JavaScript only continues to play an evermore crucial part in the web, and I wanted to provide a bunch of articles and examples of ES6+ features that I use regularly, for other developers to reference.
The aim is for these pieces to be short, in-depth explanations of various improvements to the language that I hope will inspire you to write some really cool stuff using JS. Who knows, you might even learn something new along the way. 😄
For my final installment in this series, I will tackle array and object destructuring: the most concise way to pull out values and properties into individual variables with very little effort.
Destructuring sounds so simple at first...
The destructuring assignment, first introduced with ES 2015, is one of my favorite additions to the JavaScript standard syntax. As I said above, destructuring makes it possible to unpack values from arrays, or properties from objects, into distinct variables.
This may sound simple when you first hear it, but actually putting it into effect, especially with deeply nested arrays or objects, is a little trickier to grasp. But before I get into the parts that might trip you up, let’s just talk about what destructuring looks like in both arrays, and more recently, objects.
Array destructuring
Array destructuring was introduced and finalized into the ECMAScript language before object destructuring.
And just as object and array literal expressions provide an easy way to create packages of data, at will, like so:
const a = ["alpha","beta", "gamma", "delta", "epsilon"];
The destructuring assignment of those same arrays uses similar syntax, but on the left-hand side of the assignment to define what values to unpack from the sourced variable.
Anatomy of array destructuring syntax
let b, c, more;
const a = ["alpha","beta", "gamma", "delta", "epsilon"];
[b, c] = a;
console.log(b); // "alpha"
console.log(c); // "beta"
[b, c, ...more] = ["alpha","beta", "gamma", "delta", "epsilon"];
console.log(more); // [ "gamma", "delta", "epsilon" ]
The short example above demonstrates array destructuring and using the rest pattern to assign extra values, which I wrote about here, on the array named a
.
Notice that the variables b
and c
are wrapped in brackets ([b, c])
, and are assigned to equal the a
array. [b, c] = a;
When those values are called they will take the first and second elements in the array. console.log(b); // "alpha"
and console.log(c); // "beta";
.
The rest pattern comes in with the variable more
. That variable is designed to inherit all the rest of the values in the a array by putting the rest syntax (...more)
ahead of the variable name when adding it to the array. Then, when more
is called, it prints out the rest of the values in its own array. console.log(more); // [ "gamma", "delta", "epsilon" ]
.
Alright, let’s go through some different uses for array destructuring.
Basic variable assignment
The most basic type of array destructuring is taking one array of values and assigning those values to an equal amount of new variables.
Example of basic array variable destructuring assignment
const govtBranches = [ "executive", "judicial", "legislative" ];
const [branch1, branch2, branch3] = govtBranches;
console.log(branch1); // "executive"
console.log(branch2); // "judicial"
console.log(branch3); // "legislative"
Each of the three values of govtBranches has a corresponding variable wrapped in the array brackets, branch1
, branch2
, or branch3
that it is assigned to.
Then, if any of those variables are called, they reflect one of the individual values from the govtBranches
array.
Assignment separation from declaration
Next up is when a variable can be declared first (either using the keywords let
or var
, not const
), and then have its value assigned via destructuring.
Example of variable declaration and then value assignment with destructuring
let agency1, agency2;
[agency1, agency2] = [ "FBI", "CIA" ];
console.log(agency1); // "FBI"
console.log(agency2); // "CIA"
As you can see, the variables agency1
and agency2
are declared as undefined variables first. Next, they’re wrapped in array brackets and assigned to the array containing the values "FBI"
and "CIA"
. From there, each variable can be called individually, and it represents one of the values in that array.
Default values
Interestingly, you can assign variables a default value, similar to default function parameter values, in case a value unpacked from an array turns out to be undefined.
This kind of thing may happen when there’s more variables created than values in the array it’s destructuring.
Example of default values in array destructuring
let one, two;
[one="a", two="b"] = ["c"];
console.log(one); // "c"
console.log(two); // "b"
The two variables one
and two
are assigned the default values "a"
and "b"
before being assigned to the array containing the single value of "c"
.
And when the variable one
is called after the array destructuring takes place, its value is overwritten to be "c"
due to the value in the array. The value of two
does not change though because the array doesn’t contain a second value, so that value, if it existed, would be undefined
.
Swapping variables
Did you know that two variable values can be swapped in one destructuring assignment? They can, and it’s pretty handy that you don’t need a temporary variable to make this possible anymore.
Example swapping variable values with array destructuring
let boots = "cat";
let rocky = "dog";
[boots, rocky] = [rocky, boots];
console.log(boots); // "dog"
console.log(rocky); // "cat"
Initially, the variable boots
is a "cat"
and the variable rocky
is a "dog"
, but simply by switching the order of the values in the array they’re being assigned to, their values can be swapped so boots
becomes the "dog"
and rocky
becomes the "cat"
.
This is a really useful trick in certain scenarios.
Parsing an array returned from a function
Getting an array returned from a function is nothing new, but now you can destructure the values being returned to make working with them more concise.
Example parsing an array returned from a function
function color() {
return ["red", "yellow", "green", "blue"]
}
let r, y, g;
[r, y, g] = color();
console.log(r); // "red"
console.log(g); // "green"
The color()
function returns an array of colors. By destructuring the variables r
, y
, g
against the function, those values in the array are assigned to those variables.
Ignoring some returned values
In the same vein, destructuring lets you ignore certain array values you aren’t interested in.
Example ignoring values from an array with destructuring
function ignoreColor() {
return ["indigo", "orange", "lime"]
}
const [i, ,l] = ignoreColor();
console.log(i); // "indigo"
console.log(l); // "lime"
Simply by adding an empty space in the destructured array, you can choose not to return the value "orange"
from the function ignoreColor()
.
You can also choose to ignore all the values from a function if you’d like (though I don’t really see much of a use case for that).
[ , , ] = ignoreColor();
Assigning the rest of an array to a variable
And I’m back again to part of what I showed in the very first array destructuring example: using the rest operator (...)
to pick up any leftover values from an array.
Example assigning leftover values to a variable via array destructuring
const [commanderInChief, ...staff] = ["President", "Vice President", "Chief of Staff", "Press Secretary"];
console.log(commanderInChief); // "President"
console.log(staff); // [ "Vice President", "Chief of Staff", "Press Secretary" ]
Just as before, commanderInChief
takes the first value in the array, and by using the rest syntax, ...staff
takes all the remaining values in the array as a new array of its own.
Simple as that. Now let’s take a look at how destructuring works on objects.
Object destructuring
Object destructuring takes a similar tack as array destructuring, except instead of values being pulled out of an array, properties (keys) and their values can be pulled out of an object.
Here’s some examples of how that can look.
Basic object destructuring assignment
Once again, I’ll start with the most basic example of how object destructuring.
Example of basic object destructuring
const pieIngredients = { pumpkin: "1 can", pieCrust: "1 crust", spice: "2 tsp"};
const { pumpkin, pieCrust, spice} = pieIngredients;
console.log(pumpkin); // "1 can"
console.log(pieCrust); // "1 crust"
console.log(spice); // "2 tsp"
By wrapping the properties in the object pieIngredients
and setting them equal to the object, each property, pumpkin
, pieCrust
, and spice
, becomes its own variable and the value attached to it becomes the value for the new variable.
Assignment without declaration
A variable can also be assigned its value with destructuring separate from its declaration, just like you can do it with array destructuring.
Example of assigning variables after declaring the variables separately
let hobby, sports;
({hobby, sports} = {hobby: "knitting", sports: "croquet"});
console.log(hobby); // "knitting"
console.log(sports); // "croquet"
Note the parentheses ( ... )
around the assignment statement are required when using object literal destructuring assignment without a declaration.
Otherwise, the syntax is considered invalid because the syntax on the lefthand side, the {hobby, sports}
, is considered a block and not an object literal. Putting the parentheses around the whole line though, clarifies the intent and makes it valid.
Assigning to new variable names
One helpful thing is that a property can be unpacked from an object and assigned to a variable with a different name than the object property.
Example reassigning destructured object properties to new variable names
const car = {speed: 110, color: "red"};
const { speed: fast, color: cherry } = car;
console.log(fast); // 110
console.log(cherry); // "red"
Here, for example, const {speed: fast} = car;
takes from the object car
the property named speed
and assigns it to a local variable named fast
.
Default values
Just like with array destructuring, variables for destructured objects can be assigned a default, in the case that the value unpacked from the object is undefined
.
Example of assigning default values to object destructuring variables
const { redWine = "cabernet", whiteWine = "pinot grigio"} = { redWine: "malbec"};
console.log(redWine); // "malbec"
console.log(whiteWine); // "pinot grigio"
In this example, the variables redWine
and whiteWine
are assigned default values of "cabernet"
and "pinot grigio"
. Then the redWine variable is reassigned the value of "malbec"
, but since whiteWine
is not defined in the object being destructured, it retains its original value.
Unpacking fields from objects passed as function parameters
Another feature of object destructuring is that you can actually use destructuring syntax inside of function calls to get just those values back. Take a look at this.
Example of passing destructured object properties as function parameters
const girl = {
name: "Paige",
age: 30,
eyeColor: "blue",
hair: {
type: "curly",
color: "red",
length: "shoulder-length"
}
}
const getUserName = ({name}) => {
return {name};
}
console.log(getUserName(girl)); // { name: "Paige" }
const getUserHair = ({hair: {type, color}}) => {
return `Her hair is ${color} and ${type}`;
}
console.log(getUserHair(girl)); // Her hair is red and curly
In the example here, the object girl
is a pretty standard one. It’s got two levels of nested properties but other than that, it’s unremarkable.
The thing to notice is the two functions getUserName()
and getUserHair()
. You’ll see with getUserName()
the function parameter being passed to it is actually the destructured version of the property name from the object it receives.
So when the whole girl
object is passed to the function, it returns just the property and value of name
as the function’s output.
The second function, getUserHair()
is even more interesting because the value it’s trying to access is actually located two levels down within the object being passed to the function, so first, the property of hair
must be accessed, and then the properties unique to hair, which are type
and color
can be accessed.
That function will then return a string stating the object’s hair color and hair type as the output when it’s called with the object of girl
.
This is also an example of how to access nested objects using destructuring, which I’ll talk about next.
Nested object destructuring
This was a topic that took me some time to wrap my head around (and to be honest, I usually still have to go back and look at the documentation again when I want to destructure multi-level nested objects).
The basic gist though is: if your object is more than one level deep within your object, you must first access its parent property, and its parents’ parent property, and so on, until you reach the outermost object property value.
Example of deeply nested object destructuring
const girl = {
name: "Paige",
age: 30,
eyeColor: "blue",
hobbies: {
primaries: [
{
mostFavorite: [
"drawing",
"art"
],
frequentlyDone: "cooking",
relaxing: {
reading: "fictionBooks"
}
}
]
}
}
const {
hobbies: {
primaries: [
{
mostFavorite
}
]
}
} = girl;
console.log(mostFavorite[0]); // "drawing"
console.log(mostFavorite[1]); // "art"
const {
hobbies: {
primaries: [
{
relaxing: {
reading
}
}
]
}
} = girl;
console.log(reading); // "fictionBooks"
I used the same girl
object from the previous example but I added the property of hobbies
to the object and added some new arrays and objects within them so I could show how you can pull values out of either.
The first new object I create pulls out the nested object property of mostFavorite
, which happens to be an array with two values. To reach these values, first, I have to wrap the outermost property of girl
, which is hobbies
in curly braces. Next, I have to wrap hobbies"
property of primaries
. Then, I must dive into both the array and the object that primaries
contains to reach the mostFavorite
property which actually holds the values I seek.
From there, it’s a simple exercise to log out any values that mostFavorite
has.
In the same vein, to reach the value of the property reading
buried deep within the girl
object, I have to again start out by wrapping hobbies
in curly braces, then proceed on to primaries
, dive into the array and find the object property of relaxing
and finally wrap the property of reading
, which belongs to the parent object of relaxing
.
Then, I can simply call reading
as its own variable and get back the value nested deeply in the girl
object.
It definitely takes some practice to get the hang of, but look at how much less syntax is needed to get those values than before.
So long const reading = girl.hobbies.primaries[0].relaxing.reading;
. I won’t miss that.
If you’d like to read more about nested object destructuring, I wrote another article specifically about it a few months back, as well as ways to avoid undefined errors if values weren’t available. Here’s the link to it. 😄
Rest syntax in object destructuring
Last example, which is still in ECMAScript stage 4 proposal, I might add, at the time of writing this article: rest syntax plus object destructuring.
I showed this in the very first array destructuring demo, but I haven’t shown it with object destructuring yet. Turns out, it works about the same for objects as it does for arrays.
Example of object destructuring with the rest syntax
let myObjectOfNums = {
ex: "ten",
why: "twenty",
zed: "thirty",
dee: "forty",
ee: "fifty"
}
let {ex, why, zed, ...allOthers} = myObjectOfNums;
console.log(ex); // "ten"
console.log(why); // "twenty"
console.log(zed); // "thirty"
console.log(allOthers); // { dee: "forty", ee: "fifty" }
See how easy it is to pull out the first three properties and their values from the object myObjectOfNums
, as well as using the rest parameter to keep the other properties contained together in a new object called allOthers
?
And the other rules of destructuring still apply just the same here. If you wanted to change the variable names from ex
or why to a
and b
you could do so just the same as before.
Example of object destructuring with the rest syntax AND variable reassignment
let { ex: a, why: b, zed, ...allOthers } = myObjectOfNums;
console.log(a); // "ten"
console.log(b); // "twenty"
console.log(zed); // "thirty"
console.log(allOthers); // { dee: "forty", ee: "fifty" }
This is also totally valid. Pretty cool, huh? 😃
Conclusion
Array and object destructuring is something that's existed in languages like Perl and Python for years, but it wasn’t until ECMAScript 2015 that JavaScript began to get some parity in this area.
This new syntax utilizing curly braces makes it possible to easily access individual variables from arrays, and even objects, with very concise code. And I, for one, am a big fan of it.
My goal with this series is to examine parts of the ES6 syntax you use everyday but may not know all the subtleties and nuances of, so you can be an even better web developer.
Check back in a few weeks, I’ll be writing about more JavaScript and ES6 or something else related to web development.
Thanks for reading, I hope I’ve made array and object destructuring a little more clear and something you’re excited to try out in your own code bases.
References & Further Resources
- Destructuring assignments, MDN docs
Want to be notified first when I publish new content? Subscribe to my newsletter.