Understanding let and const in JavaScript ES6

Posted: Aug 7th, 2017

JavaScript ECMAScript 6th has been around for more than 2 years now, and for every serious developer that has embraced and adopted the revised language’s syntax, there is a casual developer somewhere that has chosen to shun it. “If the old syntax still works..” has been the easy position of many a lazy JavaScript programmer.

Well, two years have passed, and the ubiquity of ES6 makes it harder and harder to continue staying on the sidelines, for any programmer with an ounce of self respect that is. In this tutorial, I’ll gently break you into the wonderful world of JavaScript ES6, by revisiting the very first thing most of us learned in JavaScript- defining variables and placeholders.

Two new ways to define variables- let and const

The keyword var has traditionally been how we’ve defined variables in JavaScript. In JavaScript ES6, however, this is being augmented with two new keywords that better represent the type of data being stored, in turn making them easier to debug and less prone to mistakes. They are:

let

const

The first thing to understand about let and const is that they are block scoped, compared to var , which is function scoped. This means they are local to the closest block (curly braces) that they are defined in, whereas var is local to the entire function, or even global if defined outside functions. More on this later.

The difference between let and const lies in that the former should be used to hold variables that are subject to change, while const , as the name implies, data that you know will stay constant. In fact, trying to reset a const ’s value after it’s been set will result in an error.

The let keyword

Use let to declare a variable when the data it’s holding may change, similarly to var :

let myage = 39 if (new Date().getFullYear == 2018){ myage = 40 }

As you can see, once I define a variable using let , I can update its value by referencing it again with the new value, without the let keyword in front of it.

Unlike var , you cannot define the same let variable more than once inside a block:

let myage = 39 let myname = 'George' let myage = 40 // SyntaxError: Identifier myage has already been declared

This helps prevent accidental overwriting of a variable once it’s declared, which happens all too frequently with var .

As touched upon already, let is block scoped, which just means it’s available inside the block (curly braces) it’s defined in (including inner blocks), but not outside it:

if (true){ // new block let myname = 'George' } console.log(myname) // syntax error, myname is undefined

Contrast that to if we had used the var keyword instead:

if (true){ // new block var myname = 'George' } console.log(myname) // logs 'George'

With var , the variable is scoped to the function, or when outside it as in the example above, to the window object itself.

Defining multiple let variables with the same name

While you can’t define the same let variable more than once in a block, there’s nothing preventing you from doing this inside a different block. The thing to remember is that ES6 script treats them as separate variables:

let mybrother = 'Paul' if (true){ // new block let mybrother = 'Jason' console.log(mybrother) // Jason } console.log(mybrother) // Paul

We’ve declared let mybrother twice, once in each block, which is valid. Each mybrother variable is distinct from the other, king of its domain inside the block it was defined in. Had we replaced let with var , console.log() would have returned "Jason" in both instances, as the second var declaration overwrites the first with the value “Jason” when encountered.

Using let inside for(...) loops

When it comes to for(var i;;) loops in JavaScript, using let instead of var to keep track of the iteration offers a unique advantage- we no longer have to use a IIFE (immediately invoked function expression) inside the loop to properly capture the value of the iteration variable at each cycle, should we need those values for later on.

Consider the following traditional for loop that tries to recall the value of the iteration variable after runtime of the loop, by using a setTimeout for example:

for (var i = 0; i < 5; i++){ setTimeout(function(){ console.log(i) }, i * 100) } //logs '5, 5, 5, 5, 5'

This fails, and all you get is the value of i at the very last iteration, not 0, 1, 2, 3, 4. The problem is that var variable is not scoped to the block it’s in, and hence accessible everywhere, either inside the function it’s defined in, or globally if outside a function. This means the variable i is being repeated overwritten during a for loop. When setTimeout tries to recall i , all it gets is the very last value i was set to.

In the past, a common way to overcome this pesky problem is by throwing in a IIFE inside the for loop to create a closure that captures the value of i at each iteration:

for (var i = 0; i < 5; i++){ (function(x){ setTimeout(function(){ console.log(x) }, i * 100) })(i) } //logs '0, 1, 2, 3, 4'

Now it works, but argh, so ugly and verbose.

Replacing var with let solves this common issue automatically, as the iteration variable defined using let scopes each instance of i to the block it’s in, creating the same result as wrapping a IIFE around the for loop:

for (let i = 0; i < 5; i++){ setTimeout(function(){ console.log(i) }, i * 100) } //logs '0, 1, 2, 3, 4'

The correct value of i gets returned regardless of the delay. This is helpful in many scenarios, such as Ajax requests inside loops that rely on the value of i to make different requests, or event handlers that utilize the value of i to perform an action tailored to the element it’s bound to. Here’s an example of the later:

let links = document.getElementsByTagName('a') for (let i=0; i<links.length; i++){ links[i].onclick = function(){ alert('You clicked on link ' + (i+1)) } }

let variables and the Temporal Dead Zone

Yes, you read it correctly- there is a term in the programming world called the Temporal Dead Zone, and it has nothing to do with zombies or time shifting, sadly.

Inside a block, the lines between the start of a block up until the point in which a let variable is initialized is fondly referred to as the Temporal Dead Zone. In that zone, attempting to reference that let variable returns a ReferenceError:

function test(){ console.log(dog) // returns ReferenceError let dog = 'spotty' }

This may almost seem obvious, but traditionally, var variables behave very differently thanks to a concept known as variable hoisting. In a nutshell, variables defined using var are automatically “hoisted” to the very top of its execution context (ie: top of a function or top of the script if it’s global variable), even if it was initialized farther down the block. This means you can reference a variable before it’s actually declared without getting a ReferenceError:

function test(){ console.log(dog) // returns undefined var dog = 'spotty' // variable auto hoisted to the very top of the function (but not its value) console.log(dog) // ‘spotty’ }

The value undefined is returned, as hoisting only moves the variable itself to the top, but not the value you may have set it to. Due to variable hoisting, the above is equivalent to the following:

function test(){ var dog console.log(dog) // returns undefined var dog = 'spotty' // hoisted to the very top of the function console.log(dog) // ‘spotty’ }

With let variables, no variable hoisting is involved, and attempting to reference a let variable before it’s physically declared inside a block will return a ReferenceError.

The const keyword

We’ve spent a lot of time discussing the let keyword, but lets not forget about its dependable side kick, const .

Use const to declare variables that will never change, such as the value of PI or the name of your brother. When another member on your dev team sees the const keyword in your code, he/she knows that that's one less variable he has to keep track of changes to.

const is similar to let in that it’s block scoped, making it only accessible within the block (curly braces) it’s defined in. In addition, it’s also subject to the Temporal Dead Zone rule.

const unlike let must be initialized with a value at the time of definition. Furthermore, it can’t be reassigned with another value afterwards:

const mydog = 'spotty' // good mydog = 'fluffy' // error: assignment to constant const mybrother // error: Missing initializer in const

While a const variable cannot be reassigned entirely to a different value, if the value of a const is an object or array, the object’s properties themselves are still mutable, able to be modified:

const myobject = {name:'George', age:39} //myobject = {name: 'Ken', age:39} //error myobject.age = 40 // OK const myarray = [] myarray[0] = 'Football' // OK

let and const versus var