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.