DEV Community

Cover image for Hoisting wasn't that tough!
Abhinav Singh
Abhinav Singh

Posted on

Hoisting wasn't that tough!

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:

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

foo(); // "Hello, World!"
function foo() {
  console.log("Hello, World!");
}
Enter fullscreen mode Exit fullscreen mode

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:

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

foo(); // "Hello, World!"
function foo() {
  console.log("Hello, World!");
}
Enter fullscreen mode Exit fullscreen mode
  • 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.

console.log(a); // undefined
var a = 5;
console.log(a); // 5
Enter fullscreen mode Exit fullscreen mode

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.

console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 10;
Enter fullscreen mode Exit fullscreen mode

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.

foo(); // "Hello, World!"
function foo() {
  console.log("Hello, World!");
}
Enter fullscreen mode Exit fullscreen mode

Function Expressions

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

bar(); // TypeError: bar is not a function
var bar = function() {
  console.log("Hello, World!");
};
Enter fullscreen mode Exit fullscreen mode

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.

{
  console.log(c); // ReferenceError: Cannot access 'c' before initialization
  let c = 20;
}
Enter fullscreen mode Exit fullscreen mode

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

console.log(x); // undefined
var x = 10;
console.log(x); // 10
Enter fullscreen mode Exit fullscreen mode

Example 2: Function Hoisting

greet(); // "Hello!"
function greet() {
  console.log("Hello!");
}
Enter fullscreen mode Exit fullscreen mode

Example 3: let and const Hoisting

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

const z = 10;
console.log(z); // 10
Enter fullscreen mode Exit fullscreen mode

Example 4: Function Expression Hoisting

console.log(sayHello); // undefined
var sayHello = function() {
  console.log("Hi!");
};
sayHello(); // "Hi!"
Enter fullscreen mode Exit fullscreen mode

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

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

foo(); // "Hello, World!"
function foo() {
  console.log("Hello, World!");
}
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (0)