Whether you are a budding JavaScript developer, or an expert looking to really learn the intricacies of the language - one question may stump you:
var
,let
,const
- what are the differences between these three keywords in JavaScript?
Some JavaScript developers stick to a golden rule to use const
unless a variable is expected to be reassigned in the future. In that case, they use let
.
However, sometimes that’s not always the case, and you might encounter code that only uses var
.
Let’s get started!
Scope
To begin our investigation into the world of JS syntax, we’ll first need to learn a little about scope.
Scope defines the accessibility of variables during the lifetime of a program.
Regarding JavaScript, there’s global scope, function scope, and block scope:
- Global scope - scope accessible anywhere throughout the program.
- Function scope - scope within a function definition.
- Block scope - scope in-between { } like in the body of a for-loop.
Take for example the following example:
// Global scope
const globalScopeVariable = "global"
function functionScope() {
const functionScopeVariable = "function"
// Block scope
{
const blockScopeVariable = "block"
// can access globalScopeVariable, functionScopeVariable and blockScopeVariable
}
// can access globalScopeVariable, functionScopeVariable
}
// can access globalScopeVariable
As we can see, the scope controls the accessibility of certain variables. We say that a certain variable is “scoped” to a certain type of scope (i.e. blockScopeVariable
is scoped to the block that it finds itself in).
The relationship between the accessibility of variables and scope can operate somewhat like a Russian doll.
For example, global scope encapsulates both function and block scope. Function and block scope work somewhat interchangeably.
For example - this is perfectly OK JavaScript:
function scopeOne() {
{
function scopeTwo() {
{
let high = "five"
}
}
}
}
As you can see - the function scopeTwo
is defined within a block.
Now that we have an understanding of scope - let’s take a look at the differences of var
and let
.
var vs let - Hoisting
Let’s start by defining two variables. One with the keyword let
and the other with the keyword var
:
let myLet = "Apple"
var myVar = "Pie"
console.log({ myLet, myVar })
// OUTPUT: { myLet: 'Apple', myVar: 'Pie' }
// **Protip** - logging variables as an object increases readability, because key-value pairs are logged to the console.
Now let’s create a function. By doing this, we’ll show a key difference by logging these two variables to the console before they are defined.
function myFunction() {
console.log({myVar})
console.log({myLet})
let myLet = "Apple"
var myVar = "Pie"
}
myFunction();
Running this yields us the following error:
{ myVar: undefined }
Thrown:
ReferenceError: Cannot access 'myLet' before initialization at myFunction
…What? 🤔 myVar
is undefined
, and myLet
returns a ReferenceError
that states that myLet
has yet to be defined.
What’s happening here?
myVar
is hoisted to the top of the function scope, and thus is considered accessible throughout the function even before it is defined with a proper value. In fact, it takes the value of undefined
.
Before continuing on, let’s take a look at how our JavaScript code is built and ran, and from this we will better understand this concept of hoisting.
Creation and Execution of JavaScript Code
When interpreting JavaScript code, there are 2 phases.
⚠️ This is a generalization. Each JS engine is different.
- “The Creation Phase” is when the JavaScript interpreter allocates space for variable and function declarations in memory.
- “The Execution Phase” is when the JavaScript interpreter starts executing our code line by line.
So what exactly happened during “the Creation Phase” in the example snippet from above? myVar
was defined a default value of undefined
.
myLet
is different. It is allocated memory, and thus technically hoisted but is considered to be in a state named the Temporal Dead Zone. In this state, myLet
is allocated memory, but this memory is technically inaccessible.
So to summarize our first difference between let
and var
:
var
can be accessed and overwritten anywhere within a function scope or global scope due to hoisting.let
also exhibits this behavior, except it cannot be accessed due to being in a state named the Temporal Dead Zone.
var vs let - Scoping differences
Another difference is behavior of let
and var
in regard to scope. Let’s take a look at this example:
var myVar = "FirstVar"
let myLet = "FirstLet"
function myFunction() {
console.log({myVar})
console.log({myLet})
{
console.log({myVar})
console.log({myLet})
var myVar = "SecondVar"
let myLet = "SecondLet"
}
}
myFunction();
Which returns to us the following:
{ myVar: undefined }
{ myLet: 'FirstLet' }
{ myVar: undefined }
Uncaught ReferenceError: Cannot access 'myLet' before initialization at myFunction
…Uh what? 🤔 myVar
is undefined
and myLet
is FirstLet
, then it become?
It looks like myLet
inherited the value FirstLet
from the global scope, and it became inaccessible when it was reassigned within that block.
It might not be obvious, but the difference we see here is that the var
defined variable, myVar
, is hoisted within the nearest function definition of which it resides, while let
does not do this. let
is hoisted to the nearest block.
This is why we are returned that myLet
is inaccessible within the block scope, but not within the function scope!
To summarize: var
is function-scoped and let
is block-scoped!
Summary of let/const vs var
So in summary the two main differences between let
(as well as const
) and var
are:
- How each are initialized in the ‘Creation Phase’
var
is initialized withundefined
let
/const
are initialized as Temporal Dead Zones and thus are considered not accessible until they are defined in the Execution Phase.
- How each are scoped
var
is function-scoped.let/const
are block-scoped.
Bonus: an additional word on hoisting
Functions can also be hoisted. Let’s take this example:
catName("Chloe");
function catName(name) {
console.log("My cat's name is " + name);
}
// OUTPUT: "My cat's name is Chloe"
This function
declaration is not like what we saw with var
where it returns undefined
. In fact, it is technically accessible when we call it before in the code!
In any case, if you find that this is annoying - you can add strict mode
to the top of your JavaScript code to disable hoisting altogether:
'use strict';