Is ‘Let’ Really Not Hoisted?
When learning JavaScript, the first thing we hear about JS variables is that var
is hoisted, whereas let
and const
are not hoisted.
But is that really true? Technically NO.
Myth #1
Hoisting: JS engine physically places the variables at the top of the code during execution.
As per MDN: Conceptually, for example, a strict definition of hoisting suggests that variable and function declarations are physically moved to the top of your code, but this is not in fact what happens. Instead, the variable and function declarations are put into memory during the compile phase, but stay exactly where you typed them in your code.
Myth #2
var
variables are hoisted. let
and const
are not.
Technically, all JS variables are hoisted. Where
let
andcost
differs fromvar
in the hoisting process is in the initialization part.
Why do we get ReferenceError
when accessing 'let'
variables before its declaration? 🤔
Variable Lifecycle
Pre-inform: A JavaScript engine interprets your code in two separate phases. Compilation phase and the Execution phase. Yes. You heard it right. JS does undergo compilation.
Moving on, a JS variable technically has 3 phases.
- Declaration phase: Variable is registered to the scope.
- Initialization phase: Variable is initialized with
undefined
. - Assignment phase: Variable is assigned a value.
Note: Please don't confuse the Declaration phase and Variable declaration: Declaration phase is done by the JS compiler and variable declaration is the explicit code written by the user ie. var foo;
Lifecycle of var
- Declaration phase: Compile-time.
- Initialization phase: Compile-time.
- Assignment phase: Execution-time.
For var
, the Initialization phase is executed right away after the Declaration phase. And they both are done during compilation.
Comparing this example with the phase diagram,
- Declaration phase + Initialization phase:
foo
is added to the scope and automatically assignedundefined
as its value during compilation. - Execution starts.
- JS checks for
foo
in the scope and finds its value asundefined.
- Assignment phase:
foo
is assigned a value of 2.
Lifecycle of let
- Declaration phase: Compile-time.
- Initialization phase: Execution-time.
- Assignment phase: Execution-time.
For let
, the Declaration phase is done during compilation. But unlike var
, the Initialization phase is not done right after the declaration phase but during the execution time.
At the start of the execution phase, the let
variables are declared, but not initialized. These variables are initialized only when the JS engine reaches the let
keyword.
Until then, the variable is said to be in the Temporal Dead Zone.
Getting or Setting uninitialized variables that are in the TDZ will result in
ReferenceError
.
Comparing this example with the phase diagram,
- Declaration phase: foo is added to the scope during compilation.
- Execution starts. Foo is not initialized at this time and does not have any value, not even
undefined
. Accessing foo at this point givesReferenceError
. - Initialization phase: When the JS engine encounters the
let
keyword, foo is assignedundefined
. - Assignment phase: foo is assigned a value of 2.
In fact, couple of other statements also behave similarly to let. Eg:
const
,class
TL;DR
- Technically,
var
,let
andconst
are hoisted. var
is declared and initialized during hoisting.let
andconst
are only declared during hoisting, not initialized. Accessing uninitialized variables result inReferenceError
.- Prefer
let
overvar
, wherever possible to avoid the confusion arising due to hoisting. - Prefer
const
overlet
, wherever possible to have strict control over the variables which should not be modified.