Demystifying Hoisting in JavaScript
Let's dive into some intriguing examples to unravel the mysteries of hoisting.
Today, let's dive into the fascinating world of JavaScript and unravel the mystery behind a concept called hoisting.
You might have come across situations where you can use a function or a variable even before they are declared. Well, that's hoisting for you!
Let's start with a simple example:
console.log(movie); // Output: undefined
console.log(getDirector()); // Output: Christopher Nolan
var movie = "Inception";
function getDirector() {
return "Christopher Nolan";
}
console.log(movie); // Output: Inception
console.log(getDirector()); // Output: Christopher Nolan
In this snippet, we declare a variable movie
and a function getDirector
but use them before their actual declarations. Surprisingly, JavaScript doesn't throw an error, and we get the expected output.
The Basics of Hoisting
Now, let's explore the concept of hoisting with another example:
console.log("Initial x:", x); // Output: Initial x: undefined
if (true) {
console.log("Inside if, y:", y); // Output: ReferenceError: Cannot access 'y' before initialization
var x = 10;
let y = 20;
console.log("Inside if, x:", x); // Output: Inside if, x: 10
console.log("Inside if, y:", y); // Output: Inside if, y: 20
}
console.log("Outside if, x:", x); // Output: Outside if, x: 10
console.log("Outside if, y:", y); // Output: Uncaught ReferenceError: y is not defined
Here, the output might surprise you.
Let's break down this code and understand the complex interplay of hoisting:
Initial
console.log
: We start by logging the initial value ofx
. Surprisingly, it printsundefined
, showcasing the hoisting mechanism in action.Inside the
if
block: Here's where it gets interesting. We attempt to log the value ofy
before its declaration. Withvar x = 10;
, the variablex
is hoisted, but it's initialized toundefined
before the assignment. However, when we try to do the same withlet y = 20;
, JavaScript throws aReferenceError
. Why? Becauselet
declarations are not hoisted to the entire block; they are only hoisted within the scope they are declared.Logging inside the
if
block: The subsequent logs demonstrate that despite the attempted log ofy
causing an error,x
is accessible and holds the value assigned within theif
block.Outside the
if
block: Moving outside theif
block, we log the values of bothx
andy
.x
retains its value of10
due to hoisting, while attempting to accessy
results in aReferenceError
.
This is due to hoisting, where variable and function declarations are moved to the top of their containing scope during the compilation phase.
Note: Execution contexts in JavaScript are created when functions are invoked, not for every block of code like
if
statements.
The complexity arises from the combination of var
and let
declarations within conditional blocks. While var
is hoisted to the entire scope, let
is confined to the block in which it is declared
.
However, things can get a bit tricky:
console.log("Before declaration - x:", x); // Output: Before declaration - x: undefined
if (true) {
console.log("Inside if - x:", x); // Output: Inside if - x: undefined
var x = 42;
console.log("Inside if, after assignment - x:", x); // Output: Inside if, after assignment - x: 42
}
console.log("Outside if - x:", x); // Output: Outside if - x: 42
console.log("Outside if, before declaration - y:", y); // Output: Outside if, before declaration - y: Uncaught ReferenceError: y is not defined
let y = 10;
console.log("Outside if, after declaration - y:", y); // Output: Outside if, after declaration - y: 10
In this case,
Before declaration of
x
: Attempting to log the value ofx
before its declaration, we getundefined
. This showcases the hoisting behavior of variables declared withvar
, which are hoisted but initialized toundefined
.Inside the
if
block: Even within the conditional block, attempting to logx
before its assignment results inundefined
. The subsequent log, after the assignmentvar x = 42;
, reveals the assigned value, showcasing the distinction between declaration and assignment during hoisting.Outside the
if
block: Once outside the block, we logx
again, and this time it retains the assigned value of42
. The attempt to logy
before its declaration throws aReferenceError
, highlighting the different behavior oflet
declarations.After the declaration of
y
: Finally, after declaringy
withlet
, logging its value now yields the assigned value of10
.
The trickiness arises from the interplay between hoisting and the nature of variable declarations in JavaScript:
Undefined with
var
: Variables declared withvar
are hoisted to the top of their scope but initialized toundefined
until their actual assignment in the code.Not Defined with
let
: Variables declared withlet
are hoisted but remain in a "temporal dead zone" until their declaration is reached in the code. Attempting to access them before declaration results in aReferenceError
.
Hoisting with Functions
Now, let's consider a scenario where we print the function itself:
console.log("Before function declaration - magicFunction:", magicFunction);
// Output: Before function declaration - magicFunction: ƒ magicFunction() {
console.log("Magic in action!");
}
magicFunction();
// Output: Magic in action!
function magicFunction() {
console.log("Magic in action!");
}
console.log("After function declaration - magicFunction:", magicFunction);
// Output: After function declaration - magicFunction: function magicFunction() { console.log("Magic in action!"); }
Let's break down this:
Before function declaration: Attempting to log the function
magicFunction
before its declaration, we getthe entire function
. This illustrates the hoisting of function declarations, where the function is hoisted to the top of the scope but not yet defined.Function invocation: Surprisingly, invoking
magicFunction
before its actual declaration doesn't result in an error. Instead, it gracefully executes the function and logs "Magic in action!".After function declaration: Logging
magicFunction
after its declaration reveals the full function definition. This showcases JavaScript's unique handling of function hoisting, allowing the function to be accessed before its declaration in the code.
In other languages, this would typically result in an error, but JS handles it gracefully.
Arrow Functions (with var) and Hoisting
Let's throw a curveball with arrow functions (with var declaration):
console.log("Before arrow function declaration - arrowFunction:", arrowFunction);
// Output: Before arrow function declaration - arrowFunction: undefined
arrowFunction();
// Output: Uncaught TypeError: arrowFunction is not a function
var arrowFunction = () => {
console.log("Arrow function magic!");
};
console.log("After arrow function declaration - arrowFunction:", arrowFunction);
// Output: After arrow function declaration - arrowFunction: () => { console.log("Arrow function magic!"); }
Arrow functions, behaving like normal variables, don't support hoisting.
Before arrow function declaration: Attempting to log the arrow function
arrowFunction
before its declaration yieldsundefined
. This is in stark contrast to function declarations, showcasing the different hoisting behavior of arrow functions.Arrow function invocation: Invoking
arrowFunction
before its actual declaration results in aTypeError
. Unlike traditional function declarations, arrow functions do not support hoisting in the same way, leading to this runtime error.After arrow function declaration: Logging
arrowFunction
after its declaration reveals the full function definition. While the variable declaration is hoisted, the arrow function itself is not fully hoisted, and thus, attempting to invoke it before declaration results in an error.
Arrow Functions (with let) and Hoisting
If arrowFunction
is declared with let
instead of var
, the behavior will change due to the differences in how let
and var
handle hoisting and variable initialization. Here's how the modified code would behave:
console.log("Before arrow function declaration - arrowFunction:", arrowFunction);
// Output: Before arrow function declaration - arrowFunction: Uncaught ReferenceError: Cannot access 'arrowFunction' before initialization
arrowFunction();
// Output: Uncaught TypeError: arrowFunction is not a function
let arrowFunction = () => {
console.log("Arrow function magic!");
};
console.log("After arrow function declaration - arrowFunction:", arrowFunction);
// Output: After arrow function declaration - arrowFunction: () => { console.log("Arrow function magic!"); }
Before arrow function declaration: Attempting to log
arrowFunction
before its declaration withlet
will result in aReferenceError
rather thanundefined
. This is because variables declared withlet
are hoisted to the top of their block (or the top of the global scope if declared outside any block), but they are in a "temporal dead zone" until the line of the declaration is reached. Accessing the variable in this zone throws aReferenceError
.Arrow function invocation: Invoking
arrowFunction
before its actual declaration still results in aTypeError
. The behavior is the same as when usingvar
.After arrow function declaration: Logging
arrowFunction
after its declaration will display the full function definition, just as in the previous example.
Function Expressions and Hoisting
Now, let's explore function expressions:
console.log("Before function expression declaration - getReview:", getReview);
// Output: Before function expression declaration - getReview: undefined
getReview();
// Output: Uncaught TypeError: getReview is not a function
var getReview = function () {
console.log("A cinematic masterpiece!");
return "A cinematic masterpiece!";
};
console.log("After function expression declaration - getReview:", getReview);
// Output: After function expression declaration - getReview: function () { console.log("A cinematic masterpiece!"); return "A cinematic masterpiece!"; }
console.log(getReview());
// Output: A cinematic masterpiece!
Surprise! Unlike function declarations, function expressions are not hoisted as-is.
Before function expression declaration: Attempting to log the function expression
getReview
before its declaration yieldsundefined
. This starkly contrasts with function declarations, emphasizing the unique hoisting behavior of function expressions.Function expression invocation: Invoking
getReview
before its declaration results in aTypeError
. Unlike function declarations, function expressions are not fully hoisted, leading to this runtime error.After function expression declaration: Logging
getReview
after its declaration reveals the full function expression definition. The variablegetReview
is hoisted, but the function expression itself is not hoisted as a fully defined function.Function expression invocation after declaration: Invoking
getReview
after its declaration works as expected, showcasing that the function expression is now fully initialized.
This example solves the puzzle of hoisting and function expressions:
Variable Declaration Hoisted: Similar to variables declared with
var
, the declaration of the variablegetReview
is hoisted to the top of the scope.Function Expression Limitation: While the variable is hoisted, the function expression itself is not fully hoisted. Attempting to invoke it before its declaration results in a
TypeError
.Temporal Dead Zone: Function expressions have a "temporal dead zone" during which they are not fully initialized, causing runtime errors when accessed before their declaration.
In Summary
Variables are Hoisted with
undefined
: Variable declarations get hoisted with an initial value ofundefined
during the memory creation phase.Function Declarations vs. Expressions: Function declarations are fully hoisted, while function expressions are hoisted but not initialized.
Arrow Functions Play it Cool: Arrow functions don't fully embrace hoisting like traditional function declarations. Arrow functions, behaving like variables, get hoisted and initialized as
undefined
.Hoisting is a mechanism in JS where variable and function declarations are moved to the top of their scope before execution.
UNDEFINED
means the variable has been declared but not assigned a value, whileNOT DEFINED
means the variable is not declared.Mind the Order: The order of declarations matters, especially when using function expressions.
So there you have it, a peek into the magical world of hoisting in JavaScript.
Thank you!