Hoisting wasn't that tough!
Tags: webdev, javascript, beginners, tutorial
Published: 2024-06-08 07:21:45

Hoisting is a unique behavior in JavaScript that often confuses beginners. It refers to the process where variable and function declarations are moved to the top of their containing scope during the compile phase. However, this is an oversimplified explanation. To fully grasp hoisting, we need to dive deeper into the JavaScript engine, understanding concepts such as the execution context, memory creation phase, and code execution phase.

In this blog, we'll explore hoisting in great detail, breaking down each phase and understanding how JavaScript handles variable and function declarations behind the scenes.

## Execution Context

To understand hoisting, we need to start with the concept of the execution context. The execution context is an abstract concept that holds information about the environment within which the current code is being executed. Each execution context has two phases:

1. **Memory Creation Phase**: During this phase, the JavaScript engine sets up the environment for the code execution. It allocates memory for variables and functions.
2. **Code Execution Phase**: During this phase, the code is actually executed line by line.

Every JavaScript program runs inside an execution context. There are three types of execution contexts:

1. **Global Execution Context (GEC)**: This is the default context in which your code starts execution.
2. **Function Execution Context (FEC)**: Each time a function is invoked, a new function execution context is created.
3. **Eval Execution Context**: Code executed inside an `eval` function has its own execution context.

### Global Execution Context

The Global Execution Context is created when the JavaScript file is initially loaded. It contains:

- The Global Object (`window` in browsers, `global` in Node.js)
- The `this` keyword, which refers to the Global Object in the global context.

### Function Execution Context

Every time a function is called, a new Function Execution Context is created. This context contains:

- A new scope chain
- The `this` value specific to the function
- Variable Object (VO)

## Memory Creation Phase

During the memory creation phase, the JavaScript engine scans through the entire code and allocates memory for all variables and function declarations. It sets up the execution environment and prepares for the code execution phase.

### Variables in Memory Creation Phase

For variables declared using `var`, memory is allocated, and they are initialized with `undefined`. This is crucial to understand why accessing a `var` variable before its declaration does not throw a ReferenceError but returns `undefined`.

### Functions in Memory Creation Phase

Function declarations are fully hoisted. This means that the entire function definition is stored in memory during the memory creation phase. As a result, you can call a function before its declaration in the code.

### Example

Consider the following code:

```javascript
console.log(a); // undefined
var a = 10;
console.log(a); // 10

foo(); // "Hello, World!"
function foo() {
  console.log("Hello, World!");
}
```

During the memory creation phase:

- `a` is allocated memory and initialized with `undefined`.
- `foo` is allocated memory, and the function definition is stored.

## Code Execution Phase

Once the memory creation phase is complete, the JavaScript engine starts executing the code line by line. During this phase, the actual values are assigned to variables, and functions are executed.

Continuing from the previous example:

```javascript
console.log(a); // undefined
var a = 10;
console.log(a); // 10

foo(); // "Hello, World!"
function foo() {
  console.log("Hello, World!");
}
```

- `console.log(a);` logs `undefined` because `a` was initialized with `undefined` during the memory creation phase.
- `var a = 10;` assigns the value `10` to `a`.
- `console.log(a);` logs `10`.
- `foo();` executes the function `foo`, which logs `"Hello, World!"`.

## Hoisting in Variables

### `var` Hoisting

Variables declared with `var` are hoisted to the top of their function or global scope. They are initialized with `undefined` during the memory creation phase.

```javascript
console.log(a); // undefined
var a = 5;
console.log(a); // 5
```

Here, `a` is hoisted and initialized with `undefined`. Hence, the first `console.log` outputs `undefined`, and the second outputs `5`.

### `let` and `const` Hoisting

Variables declared with `let` and `const` are also hoisted but are not initialized. They remain in a "temporal dead zone" (TDZ) from the start of the block until the declaration is encountered. Accessing them before their declaration results in a ReferenceError.

```javascript
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 10;
```

Here, `b` is hoisted but not initialized. Trying to access it before its declaration throws a ReferenceError.

## Hoisting in Functions

Function declarations are hoisted entirely. This means both the function name and its body are moved to the top of the scope.

```javascript
foo(); // "Hello, World!"
function foo() {
  console.log("Hello, World!");
}
```

### Function Expressions

Function expressions are not hoisted in the same way. Only the variable declaration is hoisted, not the assignment.

```javascript
bar(); // TypeError: bar is not a function
var bar = function() {
  console.log("Hello, World!");
};
```

Here, `bar` is hoisted and initialized with `undefined`. Calling it before assignment results in a TypeError.

## Hoisting with `let` and `const`

`let` and `const` declarations are hoisted to the top of their block scope but are not initialized. They are in the TDZ until the actual declaration is encountered in the code.

```javascript
{
  console.log(c); // ReferenceError: Cannot access 'c' before initialization
  let c = 20;
}
```

Here, `c` is hoisted to the top of the block but remains uninitialized, resulting in a ReferenceError if accessed before declaration.

## Practical Examples

### Example 1: Variable Hoisting

```javascript
console.log(x); // undefined
var x = 10;
console.log(x); // 10
```

### Example 2: Function Hoisting

```javascript
greet(); // "Hello!"
function greet() {
  console.log("Hello!");
}
```

### Example 3: `let` and `const` Hoisting

```javascript
console.log(y); // ReferenceError
let y = 5;

const z = 10;
console.log(z); // 10
```

### Example 4: Function Expression Hoisting

```javascript
console.log(sayHello); // undefined
var sayHello = function() {
  console.log("Hi!");
};
sayHello(); // "Hi!"
```

## Checking Hoisting in the Browser

To observe hoisting behavior, you can use the browser's developer tools. Here's how:

1. Open your browser's Developer Tools (usually by right-clicking on the page and selecting "Inspect" or pressing F12).
2. Go to the "Console" tab.
3. Type in or paste your JavaScript code and press Enter.

### Example

```javascript
console.log(a); // undefined
var a = 10;
console.log(a); // 10

foo(); // "Hello, World!"
function foo() {
  console.log("Hello, World!");
}
```

Observe the outputs in the console to see how hoisting works.

## Conclusion

Hoisting is a fundamental concept in JavaScript that affects how variables and functions are initialized and accessed. Understanding the memory creation and code execution phases is crucial to comprehending how hoisting works. Remember:

- `var` declarations are hoisted and initialized with `undefined`.
- Function declarations are hoisted entirely.
- `let` and `const` declarations are hoisted but not initialized, leading to the Temporal Dead Zone (TDZ).

By mastering these concepts, you can write more predictable and bug-free JavaScript code. Happy coding!