One of the most confusing concept in Javascript is hoisting. Understanding hoisting is crucial for writing reliable and maintainable code.
What exactly is hoisting?
When the browser encounters JavaScript code, it creates a special phenomenon known as the Execution Context. During the creation of the Variable Object, all variables declared with the var
keyword and function declarations are stored in memory even before the program actually executes, and the value of variables is set to undefined
. This means that you can use variables and functions in your code before they are formally declared. This process of storing variables and functions before execution is called hoisting.
console.log(name) //undefined
var name = 'John'
console.log(name) //John
In the example above, logging the variable name
before its initialization doesn't throw an error; instead, it returns undefined
.
Types of Hoisting
There are two main types of hoisting in javascript:
Variable hoisting
Function hoisting
Class hoisting
Variable Hoisting
Variables declared with var
, let
, and const
are all hoisted in JavaScript, but there are differences in how hoisting works with var
versus let
/const
.
Hoisting with
var
: Variables declared withvar
are hoisted, allowing them to be accessed before initialization without throwing an error, albeit returningundefined
. This behavior can be confusing for developers. To address this, ECMAScript 6 (ES6) introduced new keywords,let
andconst
, which provide a more predictable behavior.Hoisting with
let
andconst
: let and const are also hoisted, but unlike var they are not initialized and accessing them before initialization results in a ReferenceError: "Cannot access 'variable' before initialization." This behavior highlights the presence of a Temporal Dead Zone (TDZ) forlet
andconst
declarations, where the variables exist but cannot be accessed until they are initialized.
console.log(fruit) //ReferenceError: Cannot access 'fruit' before initialization
let fruit = 'Apple'
In the above example we will get ReferenceError: Cannot access 'fruit' before initialization, instead if we have used var
here instead of let
, than we get the value of fruit undefined.
What is Temporal Dead Zone(TDZ)?
The Temporal Dead Zone refers to the period from the beginning of the scope where the let
or const
variable is declared until the point where it is initialized. During this time, accessing the variable will result in a ReferenceError, indicating that the variable cannot be accessed before initialization.
let name = 'John' //TDZ for 'fruit' starts here
console.log(name) //John
//Temporal dead zone for fruit hence error
console.log(fruit) //ReferenceError: Cannot access 'fruit' before initialization
let fruit = 'Apple' //TDZ for 'fruit' ends here
console.log(fruit) //Apple
Function Hoisting
Function hoisting shares similarities with variable hoisting. Function declarations are hoisted to the top of the scope, allowing them to be invoked before their actual placement in the code.
fruits() //Bannana
function fruits() {
console.log('Bannana')
}
In the above example we can see, that we call calling fruits function before its declaration and still printing the result
Note that only functiondeclarations
are hoisted, not functionexpressions
.
If we try to call the variable that the function expression was assigned to, we will get a TypeError
in case of var and ReferenceError
in case of let and const.
//Case 1
fruits() // TypeError: fruits is not a function
var fruits = function(){
console.log('Bannana')
}
//Case 2
fruits() // ReferenceError: Cannot access 'fruits' before initialization
let fruits = function(){
console.log('Bannana')
}
Arrow functions, introduced in ES6, behaves like let, or const give ReferenceError if called before initilization.
fruits() //ReferenceError: Cannot access 'fruits' before initialization
const fruits = () =>{
console.log("apple")
}
Class hoisting
Just like let
and const
variables, classes are hoisted to the top of the scope they are defined in, but inaccessible until they are initialized.
let apple = new Fruit("apple")
//ReferenceError: Cannot access 'fruit' before initialization
class Fruit {
constructor(name) {
this.name = name
}
}
Here, we declare a class called Fruit
. We try to access this class before it was declared and get a reference error: Cannot access 'Fruit' before initialization, just like in let and const.
Summary
In conclusion, variable hoisting is a crucial aspect of JavaScript that impacts how variable declarations are processed during the compilation phase. While var
exhibits hoisting behavior, it introduces scope-related issues. The introduction of let
and const
in ES6 addresses these problems by enforcing block-level scoping and introducing the Temporal Dead Zone.
As a best practice, it is advisable to use let
and const
over var
to write more predictable and maintainable code. Understanding these concepts is essential for every JavaScript developer to produce good and bug-free code.