JavaScript Hoisting: A Comprehensive Tutorial

Hoisting in JavaScript is a mechanism where variable and function declarations are moved to the top of their containing scope (either the global scope or a function scope) during the compilation phase, before the code executes.

This allows you to use variables and functions before they are declared in the code. However, only the declarations are hoisted, not the initializations or assignments.

This tutorial will explore how hoisting works with variables (both var, let, const) and functions, highlighting the behavior of each with practical code examples.

1. Hoisting with var

Variables declared with var are hoisted to the top of their scope. However, only the declaration is hoisted, not the initialization. This means that the variable is known but not assigned a value until the line where it's initialized.

Example 1: Hoisting with var

console.log(message); // Output: undefined
var message = 'Hello, world!';
console.log(message); // Output: "Hello, world!"

Explanation: In this example, the declaration of message is hoisted to the top, so the code acts as if it were written like this:

var message;
console.log(message); // Output: undefined
message = 'Hello, world!';
console.log(message); // Output: "Hello, world!"

The variable message is declared but not initialized at the time of the first console.log(), resulting in undefined.

2. Hoisting with let and const

Variables declared with let and const are also hoisted, but they are not initialized until the line where they are declared. This creates a “temporal dead zone” from the start of the block until the declaration is encountered, where accessing the variable throws a ReferenceError.

Example 2: Hoisting with let

console.log(myName); // ReferenceError: Cannot access 'myName' before initialization
let myName = 'Alice';

Explanation: The variable myName is hoisted, but it is not initialized. Accessing it before its declaration results in a ReferenceError.

Example 3: Hoisting with const

console.log(age); // ReferenceError: Cannot access 'age' before initialization
const age = 30;

Explanation: Similar to let, the const variable age is hoisted but remains uninitialized in the temporal dead zone. Accessing it before the declaration results in a ReferenceError.

3. Hoisting with Function Declarations

Function declarations are fully hoisted, meaning both the function name and its implementation are moved to the top of their containing scope. This allows you to call a function before it is defined in the code.

Example 4: Hoisting with Function Declarations

greet(); // Output: "Hello!"

function greet() {
  console.log('Hello!');
}

Explanation: The entire function declaration is hoisted to the top, so you can call greet() before its definition in the code. JavaScript treats this as if it were written like this:

function greet() {
  console.log('Hello!');
}

greet(); // Output: "Hello!"

4. Hoisting with Function Expressions

Function expressions (including arrow functions) are not hoisted in the same way as function declarations. Since function expressions are assigned to variables, they follow the same hoisting rules as variables.

Example 5: Hoisting with Function Expressions (var)

console.log(sum(2, 3)); // TypeError: sum is not a function

var sum = function (a, b) {
  return a + b;
};

Explanation: The sum variable is hoisted, but its initialization (the function assignment) is not. This results in sum being undefined when it's called, leading to a TypeError.

Example 6: Hoisting with Function Expressions (let and const)

console.log(add(2, 3)); // ReferenceError: Cannot access 'add' before initialization

let add = (a, b) => a + b;

Explanation: Since add is declared with let, it is hoisted but not initialized. This results in a ReferenceError because it resides in the temporal dead zone.

5. Hoisting Order: Functions vs. Variables

When a function declaration and a variable declaration with the same name are in the same scope, the function declaration takes precedence in hoisting.

Example 7: Function and Variable Hoisting

console.log(typeof hoistedVar); // Output: "function"

var hoistedVar = 'This is a variable';

function hoistedVar() {
  console.log('This is a function');
}

Explanation: In this case, the function declaration hoistedVar() is hoisted above the var declaration. As a result, the first console.log() outputs “function”. The var hoistedVar declaration is then processed, but since it does not override the function declaration until it is initialized, the variable's initialization to ‘This is a variable' happens later.

6. Practical Implications of Hoisting

Temporal Dead Zone: Variables declared with let and const are in a “temporal dead zone” from the start of the block until they are declared, which means they cannot be accessed before their declaration.
Use let and const: To avoid confusion with hoisting, it's generally recommended to use let and const instead of var. This will prevent accidental use of variables before they are declared.
Declare Functions Before Use: While function declarations are fully hoisted, it is a best practice to define functions before using them to make code easier to read and understand.

7. Hoisting and Block Scope

Variables declared with let and const have block scope, which affects how hoisting works within different blocks (e.g., loops, if statements).

Example 8: Hoisting in a Block Scope

{
  console.log(city); // ReferenceError: Cannot access 'city' before initialization
  let city = 'New York';
}

Explanation: Since let is block-scoped, the variable city is only accessible within the block and follows the rules of the temporal dead zone, resulting in a ReferenceError if accessed before its declaration.

Conclusion

JavaScript's hoisting mechanism moves declarations (but not initializations) to the top of their scope. This behavior affects how variables and functions are interpreted during code execution.

Understanding how hoisting works helps prevent errors related to accessing variables and functions before they are defined.

Here's a quick recap:

var: Declarations are hoisted, but the initialization remains in place. Accessing a var variable before its declaration returns undefined.
let and const: Declarations are hoisted, but they are not initialized, resulting in a ReferenceError if accessed before the declaration (due to the temporal dead zone).
Function Declarations: Entire functions are hoisted, allowing them to be called before they are defined.
Function Expressions: Only the variable declaration is hoisted, not the function assignment.

By keeping these rules in mind, you can write more predictable and error-free JavaScript code.

Related posts

JavaScript Template Literals Tutorial with Examples

JavaScript Reflect: A Comprehensive Tutorial

JavaScript Proxy: A Comprehensive Tutorial