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
Now that you have a firm grasp on how to use let
and const
, the question becomes, should you start supplanting var
with let
and const
entirely in your code moving forward? There are many schools of thought on this, though I agree with the argument that in general, var
should be treated as the weakest signal now, used after the case for using let
and const
has been exhausted.