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");
});

ES2015 – Using const

The const keyword is another addition in ECMAScript 6 (ES6). This one can be a bit confusing depending on the tools you were using only because const has been around not as part of any official ES language specification but the V8 Engine has recognized a const keyword for sometime so you might have seen it used in Chrome or Firefox. But the idea is as the name suggests to create and initialize a read-only variable. A variable that will hold a constant value and something that you can never change. In ES6 const will have block scoping just like the let keyword. Let’s take a look at this in a test.

describe("using const",function(){
"use strict";
it("will make a variable read-only", function(){
const MAX_SIZE = 10;
// MAX_SIZE = 12; // SyntaxError
expect(MAX_SIZE).toBe(10);
});
});

Inside my test, I am using const to declare a variable named MAX_SIZE setting its value to 10. I expect MAX_SIZE to be 10  and off course the above test currently passes.


describe("using const",function(){
"use strict";
it("will make a variable read-only", function(){
const MAX_SIZE = 10;
MAX_SIZE = 12; // SyntaxError
expect(MAX_SIZE).toBe(10);
});
});

However, if I try to assign MAX_SIZE  a value of 12, the test fails. It throws a TypeError: Assignment to constant variable. This represents the semantics of true ES6 const. There will be an error if you try to assign to a const and that’s a little bit different than the const that has been around in browsers like Chrome and Firefox. What they would do is allow you to assign to a const but effectively ignore the value. They wouldn’t throw an error but they also wouldn’t change the value of that variable. In ES6 it is an TypeError.

The following code snippets will demonstrate couple of things. First, const does have block semantics and second how both let and const work when you have multiple variables with the same name.


it("can shadow outer declaration",function(){
var doWork= function(){
var x = 12;
var x = 10;
return x;
};
var result = doWork();
expect(result).toBe(10);
});

Currently, the above test has 2 lines of code inside of doWork that declare a variable x and this perfectly legal JavaScript. Running this test will result in a Pass since the value that is returned from doWork is 10. Now what if we change x to a const as done below.

it("can shadow outer declaration",function(){
var doWork= function(){
const x = 12;
var x = 10;
return x;
};
var result = doWork();
expect(result).toBe(10);
});

Running the above test, throws an Uncaught SyntaxError: Identifier ‘x’  has already been declared in the browser’s console and that’s another difference between let and const versus var because with let and const we cannot have two variables inside of the same scope that have duplicate names. If const is changed to a let as shown below we get the same error – Uncaught SyntaxError: Identifier ‘x’  has already been declared in the browser’s console.

it("can shadow outer declaration",function(){
var doWork= function(){
let x = 12;
var x = 10;
return x;
};
var result = doWork();
expect(result).toBe(10);
});

However, if we move let x = 12 outside of doWork, and run the test, then my test Passes. That’s because the x that is defined inside of doWork is hiding or shadows the x that is defined outside of that function. Inside of doWork I am working with a variable x that holds the value 10.


it("can shadow outer declaration",function(){
let x = 12;
var doWork= function(){
var x = 10;
return x;
};
var result = doWork();
expect(result).toBe(10);
});

But outside of doWork I can be working with a variable x that holds the value 12  and so when I am assigning x inside of doWork I am not writing into that outer x.


it("can shadow outer declaration",function(){
let x = 12;
var doWork= function(){
var x = 10;
return x;
};
var result = doWork();
expect(result).toBe(10);
expect(x).toBe(12);
});

Changing let to const in the test also passes the test since const x = 12 is defined in a different block, a different scope.


it("can shadow outer declaration",function(){
const x = 12;
var doWork= function(){
var x = 10;
return x;
};
var result = doWork();
expect(result).toBe(10);
expect(x).toBe(12);
});

To demonstrate const has a block scoping,

it("can shadow outer declaration",function(){
if(true){const x = 12;}
var doWork= function(){
var x = 10;
return x;
};
var result = doWork();
expect(result).toBe(10);
expect(x).toBe(12);
});

wrapping const x = 12 in an if block and running the test results in a failure with an error message of ReferenceError: x is not defined.

Finally, the inner x will also work if var was replaced with a let.


it("can shadow outer declaration",function(){
if(true){const x = 12;}
var doWork= function(){
let x = 10;
return x;
};
var result = doWork();
expect(result).toBe(10);
expect(x).toBe(12);
});

ES2015 – Using let

The first new feature we will look at is a new keyword in JavaScript, the let keyword. let allows us to define variables. We always had the ability to define variables in JavaScript and we have done this with the var keyword. However, the var keyword has some limitations when it comes to scope. The scope of a variable defines the area of a program where the variable is legal to use. But with var there are only two types of scope for a variable. There is a global scope which is where I would place a variable if I define the variable with var outside of any function and then there is function scope for variables defined inside a function and there is no block scope. And this is the source of confusion and occasionally bugs particularly if you come from a background of using one of the other many languages that use curly braces to define blocks of code.

var doWork = function(flag){
if(flag){
var x = 3;
}
return x;
};

In the above code snippet, it looks like I have a variable defined as x that lives inside of the if statement but in reality because var can only give me function scoped variables, x is available throughout that function and the return keyword is going to work perfectly well because there is going to be a variable named x inside of doWork and that is the problem that  let will solve.

var doWork = function(flag){
if(flag){
let x = 3;
}
return x;
};

The let keyword will give us true block scoping. The above code snippet will generate an error if there is no other x variable defined in the outer scope. There will be an error because x is only available to use inside of the if block.

Moving forward when we are all programming with this new version of JavaScript it will be a good idea to use the let keyword instead of the var keyword to avoid this implicit variable hoisting and some of the confusing behavior that it can produce. The block scoping also works for a for statement. In the future then, let will be the replacement for var because it will allow us to declare variables where we need them, it will avoid hoisting and it will give us true block scoping which is what most developers expect.