Javascript hoisting
Hoisting is a JavaScript mechanism where variables and function declarations are moved to the top of their scope before code execution. Let's discuss why we are able to call functions before they are written in the code.
Read the code below and predict your answer without running it.
console.log(greetings);
var greetings = "Hello World!"
It might surprise you that even though we are assigning variable greetings after we console.log it! this code doesn't fail or throw an error, instead, it logs undefined in the console.
This is because the JavaScript interpreter splits the declaration and assignment of functions and variables. It "hoists" your declarations to the top of their containing scope before execution. Hence, no matter where functions and variables are declared, they are moved to the top of their scope regardless of whether their scope is global or local.
Variable Hoisting
In javascript we declare variables using var, let, and const. In our code we generally declare and initialize a variable in one line, however, javascript always declare and then initialize the variable behind the scene. In fact, variable declarations are processed before any other code is executed
var a // declaration
a = 100 // initialization or assignment
var b = 50 // declaration and initialization
Javascript Interpreter runs through the program and declares all the var variables and assigns undefined as their default values. After assignment or initialization for the variable is executed, undefined is replaced by the new value.
var greetings;
console.log(greetings); // undefined
greetings = 'Hello World!';
console.log(greetings); // "Hello World!"
If you use a variable before its declaration, it will throw a Reference Error instead of undefined.
An undeclared variable only exists in the program after the code assigning them is executed. Therefore, an undeclared variable is implicitly created as a global variable when the assignment is executed.
function func() {
a = 2
var b = 10
}
console.log(a)
// output: 2 -- This is because a is undeclared and hence it's a global variable
console.log(b)
// output: ReferenceError: b is not defined
// We are getting the error, since b is a declared variable inside the scope of func() function
Note: To prevent future bugs, it is recommended to always declare variables regardless of whether they are in a function or global scope.
ES6
Using a variable before its declaration is usually not desirable, and may cause future bugs in your program. To resolve this ES6 introduced let and const variables in 2015.
Variables declared with let and const are hoisted but not initialized with a default value. Accessing a let or const variable before it's declared will result in a ReferenceError.
console.log(a); // Uncaught ReferenceError: Cannot access 'a' before initialization
console.log(b); // Uncaught ReferenceError: Cannot access 'b' before initialization
let a = 10;
const b= 5;
In the above example, the Reference error is due to the Temporal dead zone (TDZ).
A variable is in its temporal dead zone starting from the beginning of its enclosing scope and ends when it is declared. Accessing the variable in this TDZ throws a ReferenceError.
However, using a let variable after declaring it but before its initialization will give Undefined:
let num;
console.log(num); // undefined
num = 10;
Here, javascript returns undefined, because num is accessed before assigning any value to it. Thus, JavaScript defaulted its value to undefined. This behavior is only for let variables, while declaring a const variable its value must be assigned while declaring it.
Function Hoisting
In javascript, Function declarations are also hoisted. Function hoisting allows us to call a function before it is defined.
greetings(); // "Hello World!!"
function greetings() {
console.log("Hello World!!")
}
It should be noted, that only function declaration are hoisted, and not function expressions. Since function expressions are functions assigned to a variable, variable assignments aren't hoisted in Javascript.
If we call the variable to which our function expression is assigned before initializing it, we will get an error, depending on whether the variable is block scope or global scope.
func1(); // Uncaught TypeError: func1 is not a function
var func1 = function () { }
func2(); // Uncaught ReferenceError: Cannot access 'func2' before initialization
let func2 = function () { }
func3(); // Uncaught ReferenceError: Cannot access 'func3' before initialization
const func3 = function () { }
If the function is never declared then we get a Reference error, different from the above example:
func(); // Uncaught ReferenceError: func is not defined
Conclusion
It is recommended to use let and const for your variable declaration, as using var declaration can cause unexpected bugs if accessed before initialization.
In case, you are working on an older codebase and/or can only use var, then it is best to write all the variable declarations as close to the top of their scope as possible.
Always avoid undeclared variables for both local and global scope, to prevent potential future bugs.
Function hoisting can help in cleaner code - At the top of our code, we focus on what the program is doing and push all the function declarations to the bottom. This helps the reader to quickly focus on what the program is doing, and then read the respective declaration at the bottom.
You can achieve better readability by separating function declarations into their unique modules and importing the required function into the main program.