The var
, let
and const
keywords
At this point, the let
and const
keywords are pretty much ubiquitous. Before their existence, we could only use the var
keyword to declare variables which store our data. Although you will still encounter var
in JavaScript code today, let
and const
bring with them some features which help address certain issues you might encounter when using var
.
Scope
One of the differences between let
/ const
and var
relates to how they handle scope.
Scope refers to the accessibility of variables and functions in different parts of a program. Being a lexically scoped language, the accessibility of variables and functions in JavaScript depends on where they are declared in the program. Both let
and const
are block scoped whereas var
is not. This means that variables declared with let
and const
within if/switch statements, for/while loops and curly braces { }
are not accessible outside of this block.
Let’s see how this block-scoped behaviour affects our code:
for (var i = 0; i < 3; i++) {
console.log(i)
}
console.log(`var i = ${i}`)
for (let j = 0; j < 3 ; j++) {
console.log(j)
}
console.log(`var j = ${j}`)
// OUTPUT -->
// (first for loop - using var)
// 0
// 1
// 2
// "var i = 3"
// (second for loop - using let)
// 0
// 1
// 2
// "error"
// "ReferenceError: j is not defined"
In the above example we have two for loops. In the first loop, we use the var
keyword to declare variable i
. Despite the fact that i
is within a for loop, we are still able to access it outside the for loop.
In the second loop we use the let
keyword to declare variable j
. We try to access j
outside of the for loop but we receive the ReferenceError: j is not defined
. As it was declared using let, it is block scoped and therefore only accessible within the loop.
This same behaviour occurs when using the const
keyword to declare variables within block scopes:
for (let i = 0; i < 3; i++) {
const myName = 'Pippa';
console.log(`${myName} -> ${i}`)
}
console.log(myName);
// OUTPUT -->
// (console.logging within the for loop)
// "Pippa -> 0"
// "Pippa -> 1"
// "Pippa -> 2"
// (console.logging outside the for loop)
// "error"
// "ReferenceError: myName is not defined"
Here we have declared the variable myName
using const
inside a for loop. Although we can access myName
inside the for loop as demonstrated by the console.log(${myName} -> ${i})
, we cannot access it from outside the loop and we receive a ReferenceError
when we try.
Redeclaration and Reassignment
Another way in which let
/ const
differ from var
relates to whether or not they can be redeclared or their values reassigned. Variables declared with var
can be both redeclared and reassigned.
var myName = 'Pippa';
console.log(myName);
var myName = 'Philippa';
console.log(myName);
myName = 'Pippa Again'
console.log(myName)
// OUTPUT -->
// "Pippa"
// "Philippa"
// "Pippa Again"
// No errors, we can declare myName, redeclare it and finally reassign the value.
This can sometimes cause issues, especially when working with global variables. It’s one of the reasons that let
and const
were introduced because when using var
you could end up accidentally redeclaring variables.
Let’s try the same using the let
keyword:
let myName = 'Pippa';
console.log(myName);
let myName = 'Philippa';
console.log(myName);
// OUTPUT -->
/// "SyntaxError: Identifier 'myName' has already been declared"
Here, we receive a syntax error informing us that the myName
variable has already been declared. So with let
, we cannot redeclare a variable. We can, however, reassign values:
let myName = 'Pippa';
console.log(myName);
myName = 'Philippa';
console.log(myName);
// OUTPUT -->
// "Pippa"
// "Philippa"
No errors! Rather than redeclaring myName
we just reassign its value, which is fine by let
.
So how does const
deal with redeclaration of variables? const
, short for constant, displays the same behaviour as let
when it comes to redeclaring variable, i.e. it does not allow variable redeclaration:
const myName = 'Pippa';
console.log(myName);
const myName = 'Philippa';
console.log(myName)
// OUTPUT -->
// "Pippa"
// "SyntaxError: Identifier 'myName' has already been declared
The same as with let
, we receive a SyntaxError
when trying to redeclare a const
variable. But what about reassigning the value of a const
?
const myName = 'Pippa';
console.log(myName);
myName = 'Philippa';
console.log(myName)
// OUTPUT -->
// "Pippa"
// "error"
// "TypeError: Assignment to constant variable.
That’s also a big no-no. This time we receive a TypeError
telling us we have assigned a constant variable, which cannot be changed. As such, const
is a good option to use for variables which you know won’t change.
While useful in some cases, doesn’t this behaviour seem a bit inflexible? Let me expand a bit more on the details of this. As we just saw, when using const
to store primitive data types (Number
, String
, Boolean
, null
, undefined
, BigInt
and Symbol
) we cannot redeclare or reassign the value. However, this is not the case when using const
to store non-primitive data types (objects, which include arrays and functions).
When storing non-primitive data types, although we cannot redeclare them, we can mutate them. An example of this would be using const
to store an object and then later adding on a new property or method.
The reason for this relates to how JavaScript stores different types of data. When we store an object (or any non-primitive data type) in a variable, for example const myObject = {...}
, we are saving a reference to the object (essentially an “address” to a point in memory in the JavaScript memory heap) rather than storing the object itself. Primitive types in JavaScript are stored in a different way; the actual value is stored as opposed to a reference to a point in memory.
This means that we can use const
to store an object, and later mutate this object as it is a non-primitive data type:
const carObject = {
'make': 'Ford',
'wheels': 4
}
console.log(carObject);
carObject.colour = 'red';
console.log(carObject);
// OUTPUT -->
/*
FIRST CONSOLE.LOG()
[object Object] {
make: "Ford",
wheels: 4
}
SECOND CONSOLE.LOG()
[object Object] {
colour: "red",
make: "Ford",
wheels: 4
}
*/
As you can see, we declared carObject
using const
and were able to add the colour property on later as the reference to the object which was saved in carObject
has not been changed.
Hoisting
Another major difference between let
/ const
and var
is their behaviour relating to hoisting.
Before we dive into these differences, let’s first discuss what hoisting actually is. To do so we must take a look at the 2 phases of the JavaScript execution context.
When a program is passed to a JavaScript engine there are 2 phases which will occur. JavaScript will first parse the file in what it known as the creation phase. During this phase, JavaScript will allocate memory for all variables and functions, amongst other processes such as creating the execution context and the scope chain.
Next comes the execution phase during which, as the name suggests, the JavaScript engine actually executes the code in the file.
Hoisting happens during the creation phase, and it is the process of JavaScript identifying any variables and functions in a file and “hoisting” them to the top of their respective scope so that they are available for use during the execution phase. However, as we have already discovered, not all variables are created equal, so how does the behaviour of let
/const
differ from var
? Let’s take a look.
Var
Any variable that has been declared with var
will be partially hoisted. The identifier name will be stored in memory but the value will be set to undefined
until JavaScript reaches the assignment of the value during the execution phase.
console.log(myVar)
var myVar = 'I used to be undefined';
console.log(myVar)
// OUTPUT -->
// undefined | first console.log()
// "I used to be undefined" | second console.log()
No errors were thrown, but the value of myVar
was set to undefined
until the execution phase during which JavaScript assigns the value as the string ‘I used to be undefined’.
Let and Const
Variables declared with let
and const
will be hoisted, but their values will be set to uninitialised
. This is different from var
as any values declared by var will initially be stored as undefined
until the execution phase.
Let’s take a closer look at using let:
console.log(myLet)
let myLet = 'I used to be uninitialised';
console.log(myLet);
// OUTPUT -->
// "error"
// "ReferenceError: Cannot access 'myLet' before initialization
The reference error message we received confirms this; Cannot access 'myLet' before intialization
. So, JavaScript is aware of its existence, but it has not yet been initialised with any value. Practically speaking, what this means is that you should declare and assign the value when using let
before you try and access it in your code - unless you want to receive an error that is!
That being said, some potentially unexpected behaviour occurs when we:
- declare a variable with
let
but don’t assign it a value - access that variable
- then assign it a value
That sounds a bit confusing, so let me show you what I mean with an example:
let partiallyHoisted;
// declare a variable using let but don't manually assign it a value
console.log(partiallyHoisted);
// access the variable
partiallyHoisted = 'I used to be undefined';
// assign a value to the variable
console.log(partiallyHoisted)
// OUTPUT -->
// undefined | first console.log()
// "I used to be undefined" | second console.log() after assigning the variable a value
No errors! In this case, although we tried to access partiallyHoisted
before we assigned it a value, as we had declared it (and therefore JavaScript “knew” it existed) it is set to undefined
by default, until we manually assign it a value.
This behaviour differs between let
and const
. When using const
we cannot declare a variable without assigning it a value, and if you try you will receive a syntax error:
const myConst;
console.log(myConst);
myConst = 'This will cause an error!';
console.log(myConst)
// OUTPUT -->
// "error"
// "SyntaxError: Missing initializer in const declaration"
Aside from the example we just looked at, const
behaves the same way as let
when it comes to hoisting in JavaScript. The identifier name will be hoisted to the top of its scope, but its value will be uninitialised and therefore inaccessible until JavaScript reaches the line of code which assigns it a value during the execution phase.
console.log(myConst);
const myConst = 'I will be uninitialised ';
console.log(myConst);
// OUTPUT -->
// "error"
// "ReferenceError: Cannot access 'myConst' before initialization"
If we take a look at the error message we receive, we see that it’s the same error we got when trying to access a variable declared with let
before assigning it a value. It cannot be accessed as it’s not yet been initialised. There’s a term for this specific period during which let
and const
variables have been hoisted (so JavaScript “knows” they exist) but not initialised so are therefore inaccessible; the Temporal Dead Zone.
As a final note, although you will continue to see the use of the var
keyword in JavaScript code, let
and const
are nowadays considered standard, with const
usually being used to store objects and arrays even if you plan on mutating them later on in the program.
If you made it this far, thanks for reading!
Top comments (1)
The important point almost everyone misses on these multiple "var, let, const" posts is that you can create variables without using any of those keywords. It's not a particularly good idea as those variables will be global, but it IS possible (not inside a module though, as those are automatically in strict mode)