ES2015 – Destructuring

When we declare variables with var, let and const we typically also want to initialize the variable by assigning a value. One of the new features of ES6 is the ability to use a destructuring assignment. Destructuring is an operation you might have seen in other languages like Python or Ruby. The destructuring operation allows us to assign values to a set of variables by destructuring or literally tearing apart and pattern matching a complex data structure like an array or an  object full of properties.


describe("destructuring",function(){
"use strict";
it("can destructure arrays",function(){
let x = 2;
let y = 3;

[x, y] = [y, x];

expect(x).toBe(3);
expect(y).toBe(2);
});
});

The line [x, y] = [y, x]  is a destructuring assignment which says give the variables x and y the values y and x. We are not creating a new array but instead are moving the value 3 from the y into the x and the value of 2 from the x into the y. The end result is that we swapped those values in x and y. This is one example of destructuring but there is lot more that we can do.

It is very important to understand that what we see on the right hand side of the destructuring assignment is an array. It’s an array built with the values that are in y and x. What we see on the left hand side of the destructuring assignment is not an array. The syntax looks like as if we are am building an array literal but instead we are just working with individual variables x and y. They are surrounded by square brackets since we are destructuring an array. In other words we are telling JavaScript to take the first value of that array and put it into x  and take that second value of that array and put it into y.

It is a little more obvious that we are working with individual variables if we get rid of those 2 lines of code that explicitly create x and y and instead use the let statement to say let there be a variable x that will be initialized to have the first value in some array and let there be a variable y that takes the second value as show below.


it("can destructure arrays",function(){
let [x, y] = [3, 2];
expect(x).toBe(3);
expect(y).toBe(2);
});

We can even have this array [3, 2] be returned from a function as shown in the below function.


it("can destructure arrays",function(){
var doWork = function(){
return [3, 2];
}
let [x, y] = doWork();
expect(x).toBe(3);
expect(y).toBe(2);
});

If the contents within the array were [1, 3, 2], running the below test would result in a fail since x is going to get the value of 1.


it("can destructure arrays",function(){
var doWork = function(){
return [1, 3, 2];
}
let [x, y] = doWork();
expect(x).toBe(3);
expect(y).toBe(2);
});

If that’s not what we want, if we want x to take the second value and y to take the third value inside of the destructuring assignment we can use a comma with no symbol there in any position where we want to skip the value as shown in below code snippet. We can place the empty comma in the beginning, at the end or anywhere in the middle.


it("can destructure arrays",function(){
var doWork = function(){
return [1, 3, 2];
}
let [, x, y] = doWork();
expect(x).toBe(3);
expect(y).toBe(2);
});

Along the same lines, if we had an additional variable z that’s trying to get to a member of that array that doesn’t exist. Its not returned by doWork because it only returned 1,3 and 2. What would be the value of z to be? Well, generally in JavaScript if we try to get to something that is not there like the return value of a function that doesn’t return something or the value of a function parameter that wasn’t passed, you will get undefined and that’s also what happens in the below code snippet. Running the below test results in a pass because z will get an undefined value.


it("can destructure arrays",function(){

var doWork = function(){
return [1, 3, 2];
}

let [, x, y, z] = doWork();
expect(x).toBe(3);
expect(y).toBe(2);
expect(z).toBeUndefined();
});

This is how array destructuring works. Now lets look at a similar scenario where we have a function called doWork but this time instead of returning an array, its returning an object with firstName, lastName and twitter properties.


it("can destructure objects",function(){
let doWork = function(){
return{
firstName: "Vivekanand",
lastName: "Rao",
twitter: "viveksrao"
};
};
let { firstName: FirstName,twitter: TwitterHandle} = doWork();
expect(FirstName).toBe("Vivekanand");
expect(TwitterHandle).toBe("viveksrao");
});

Now what can be a little bit confusing about this destructuring – let { firstName: FirstName,twitter: TwitterHandle} = doWork(); is it looks like we are building an object literal which is not the case. Instead we are defining individual variables and assigning them values from the object that is returned from doWork. Because it’s an object that’s why we make use of the curly braces. In this syntax let { firstName: FirstName,twitter: TwitterHandle} = doWork(); the way to think about this is to say we are going to take the value of the firstName property from the object returned by doWork and put it into a variable called FirstName. In other words what is on the right hand side of the colon that is defining the variable.

We can also drill into complex objects. Let’s say instead of having twitter as a top level property we have a property called handles and inside of handles we might have several things like Twitter, Facebook and LinkedIn as shown below.


it("can destructure objects",function(){
let doWork = function(){
return{
firstName: "Vivekanand",
lastName: "Rao",
handles:{
twitter: "viveksrao",
linkedin: "vivekanandrao",
facebook:"raosvivek"
}
};
};
let {
firstName: FirstName,
handles:{twitter: TwitterHandle},
handles:{linkedin: LinkedInHandle}
} = doWork();
expect(FirstName).toBe("Vivekanand");
expect(TwitterHandle).toBe("viveksrao");
expect(LinkedInHandle).toBe("vivekanandrao");
});

Now there is a shortcut syntax. If we prefer using a variable name that is the same as the property name that we are trying to retrieve then we don’t have to explicitly specify the variable name. We can just say write the code as shown below.


it("can destructure objects",function(){
let doWork = function(){
return{
firstName: "Vivekanand",
lastName: "Rao",
handles:{
twitter: "viveksrao",
linkedin: "vivekanandrao",
facebook:"raosvivek"
}
};
};
let {
firstName,
handles:{twitter},
handles:{linkedin}
} = doWork();
expect(firstName).toBe("Vivekanand");
expect(twitter).toBe("viveksrao");
expect(linkedin).toBe("vivekanandrao");
});

Object destructuring can be quite effective at deconstructing and pattern matching against objects that are being passed around in a system. It also works when you are defining a function as shown in the below test snippet. In this test we are trying to invoke a function called doWork. It’s being passed a string parameter and a complex parameter that involves data and caching.


it("works with parameters", function(){

let doWork = function(url,{data, cache}){
return data;
};

let result = doWork(
"api/test",{
data: "test",
cache: false
}
);
expect(result).toBe("test");
});