Hey, hopefully by now if you have been following this series you feel you have a better understanding of ES6 and the new features it brought it. This article though, is about one of the nicest features to appear with ES6, Object and Array Destructuring. Personally I love this feature, not only does it make the code easier to read, but also easier to write (in my opinion).
Just gimme an example
Ok, so you want an example hey? Well let's have a look at what we would have done with Objects and Arrays in ES5...
var expenseObj = {
type: 'Business',
amount: '10GBP'
};
var expenseArr = [
'Business',
'10GBP'
];
var objType = expense.type;
var objAmount = expense.amount
var arrType = expense[0];
var arrAmount = expense[1];
Those way's of working aren't awful, at the end of the day they have been (and still do) work and you'll see them being used. Let's take a look at ES6's way of working though...
ES6 Destructuring Objects
So, ES6 allows you to create variables based on the key within an object so you can stop with having to type out .propertyName
. What do I mean? Let's have a look:
const file = {
extension: 'jpg',
name: 'post',
size: 1024,
blah :'meh'
}
//If this were an ES5 Function
function fileSummary(file) {
debugger;
return `The file ${file.name}.${file.extension} is of size: ${file.size}`
};
//But in ES6
function fileSummary({name, extension, size}) {
debugger;
return `The file ${name}.${extension} is of size: ${size}`
};
console.log(fileSummary(file)); // In both cases returns "The file post.jpg is of size 1024" in a console.log
Personally, I really like the ES6 way because at the very declaration of the function I know what values I will be using from the object we pass in. The character count in the examples is marginally less with ES6, but imagine a larger function where you reference the name more often, it soon becomes tiresome having to write something like file.name
and that is relying on the developer using nice short names! An added benefit of ES6 Object Destructuring is that you no longer have to remember the order of the parameters if a function destructures an object such as in the below example:
// Without ES6 Object Destructuring we might have a function like this
function signup(username, password, email, dateOfBirth, city){
//create the user
}
//and we would call it as below
signup('Bob','password', 'me@me.com', '1/1/1991', 'Lowestoft')
// Bring in ES6 and Object Destructuring
function signup({username, password, email, dateOfBirth, city}){
//create the user
}
// You may have created an object to be updated as a form is filled out to capture the data
const user = {
username:'Bob',
email: 'me@me.com',
password:'password',
city: 'Lowestoft',
dateOfBirth: '1/1/1991'
}
// Note how the user object and the function as in different orders, with destructuring this is fine!
signup(user)
ES6 Destructuring Arrays
Array destructuring is handled in exactly the same way as objects, but the outcome is very different. With array's we don't have a name/key so whilst we can still reference array elements, we need to name them in our destructure and we would want one per array entry. Let's get an example:
const companies = [
'Google',
'Facebook',
'Uber'
]
// In ES5 we would reference array entries like this
var firstCompany = companies[0];
console.log(firstCompany) // Returns Google in a console.log
//In ES6 we can destructure the array and generate our variables from the positional entries in the array
const [ companyName, companyName2, companyName3] = companies;
console.log(companyName); // Returns "Google" in a console.log
console.log(companyName2); // Returns "Facebook" in a console.log
console.log(companyName3); // Returns "Uber" in a console.log
Above you can see we create a variable name for each entry in the companies array, if you have a large array of course this could be quite cumbersome to use, so it might not be the right solution for your scenario.
// We can also use the ...rest operator to obtain a reference to the rest of the items in the array
const [companyName, ...rest] = companies;
console.log(companyName); // Returns "Google" in a console.log
console.log(rest); // Returns the rest of the array items in a console.log as an array ["Facebook", "Uber"]
Above, you can see we can use the rest operator when destructuring the array, we assign the variable of companyName
to the first item in the array, and the rest are assigned to rest
variable created using the rest operator. Later on in this article I'll show an example where you can create a recursive function using this very syntax.
How about mixing the two, Object and Array Destructuring together
It is possible to combine Object and Array Destructuring together in order to quickly get to an item. In the example below we have an array of companies, and their location, we then use Destructing to get the companyLocation
from the object positioned first in the array.
const companies = [
{name: 'Google', companyLocation:'Mountain View'},
{name: 'Facebook', companyLocation:'Menlo Park'},
{name: 'Uber', companyLocation:'San Francisco'},
]
const [{companyLocation}] = companies // Gives you the first array members location
console.log(companyLocation); // Returns Moutain View in a console.log
We can also use Destructuring in the opposite way, fetching the array item from within an object instead. Below you'll see we have an Object for Google which contains a key for locations
. The value of that key is an array of locations. Our destructuring line first destructures the object, looking just at the locations key, we then destructure the array, fetching the first entry and assigning it the variable name of firstLocation
.
const Google = {
locations: ['Mountain View', 'New York', 'London']
}
const { locations: [firstLocation] } = Google
console.log(firstLocation) // Returns Mountain View in a console.log
Some more complex examples
Converting an array of array's into an array of objects
During some training courses I came across test examples where I was supplied with an array which contained arrays of map co-ordinates. This array needed to be converted to an array of objects so they could be named as x and y. For this I ended up using the .map
array helper as you'll see below:
const points = [
[4,5],
[10,1],
[0,20]
];
//Requirement
/* [
{x: 4, y:5},
{x: 10, y:1},
{x:0, y:20},
] */
//destructure the array
const newPoints = points.map(([x, y]) => {
return {x, y}
})
console.log(newPoints) // This returns the requirement in a console.log
A Udemy course that I followed through some of these learnings set a really complex test at the end of its section on Object and Array Destructuring which took a while to figure out. I'm going to include it below and add an explanation of what is happening. Maybe you want to give it a shot and let me know your solutions :)
The task:
Use array destructuring, recursion, and the rest/spread operators to create a function 'double' that will return a new array with all values inside of it multiplied by two. Do not use any array helpers! Sure, the map, forEach, or reduce helpers would make this extremely easy but give it a shot the hard way anyways
The supplied starter code:
const numbers = [1, 2, 3];
function double() {
};
My solution (Look at the end of this article, there can be an issue with this solution):
const numbers = [1, 2, 3];
function double([firstNum, ...rest]) {
if(!firstNum) return[];
return [firstNum * 2, ...double(rest)];
};
double(numbers);
Explanation of the solution:
We start the above with an array of numbers, in our function what is happening is as follows:
- The arguments for the function include a deconstructed array value
firstNum
, and then we use the...
rest parameter (helpfully called rest here) - if
firstNum
is falsy then we return an empty array - In our return call we multiply was number we were given multiplied by 2, and the second entry is the recursive call to double using the
...
spread oprator, ultimately making the call look like this in the first iterationdouble(2,3)
and thendouble(3)
in the second iteration. This nesting/recursion will output2,4,6]
. If you add in someconsole.log
's it can look a little confusing because of the recursion, for example:
const numbers = [1, 2, 3];
function double([firstNum, ...rest]) {
console.log(`firstNum: `);
console.log(firstNum);
console.log(`rest: `);
console.log(rest);
if(!firstNum) {
console.log(`!firstNum`)
return [];
}
var retValue = [firstNum * 2, ...double(rest)];
console.log(`retValue: `);
console.log(retValue);
return retValue;
}
var endResult = double(numbers);
console.log(`endResult: `);
console.log(endResult);
Below is the output from the console.log messages
firstNum:
1
rest:
[2, 3]
firstNum:
2
rest:
[3]
firstNum:
3
rest:
[]
firstNum:
undefined
rest:
[]
!firstNum
retValue:
[6]
retValue:
[4, 6]
retValue:
[2, 4, 6]
endResult:
[2, 4, 6]
The output may seem confusing because it shows [6]
, then [4,6]
, and then [2,4,6]
this is because of the recursion. You are running the first iteration, inside that you call a second iteration, and inside that you call a third iteration, and inside that a fourth. The fourth iteration finishes returning an empty array, this in turn finalises the third iteration, adding 6 to the array, this in turn finalises the 2nd iteration, thus adding 4 to the front of the array and finalises the 1st iteration, prepending 2 to the returned array. Finally giving you [2,4,6]. If the return []
was not present when we had got no firstNum
value then we would infinitely run through until the browser ran out of memory and threw a Stack Overflow
error.
Whoops!!
So, it's been highlighted to me that actually there is an issue with the solution code (goes to show that tutorials aren't always 100%!). Let's say the supplied array contained a 0. A zero is considered falsy when it comes to evaluating the value! so we would get an empty array returned from the double
function. We can fix that though:
const number = [0, 1, 2, 3, 4]
function double([firstNum, ...rest]) {
if(typeof firstNum === "undefined") return[];
return [firstNum * 2, ...double(rest)];
};
double(numbers);
I could have just changed the example above but I thought it would be more beneficial for everyone to see where a mistake can easily occur like this.
Top comments (2)
nice article. I have a notice of using object destructuring:
when you destructuring methods(which use "this" inside) in obj and call them, "this" will be called in different context.
I'm not too sure I follow, do you have example of what you mean??