DEV Community

Cover image for The Curious Case of JavaScript Hoisting
Corina: Web for Everyone
Corina: Web for Everyone

Posted on • Updated on

The Curious Case of JavaScript Hoisting

Giving Code Elements a Head Start in the Race to Execution!

Ever played with a yo-yo? In JavaScript, variables and traditional function declarations act much like one: they start at the bottom but magically make their way to the top!

Welcome to the fascinating realm of hoisting where function and variable declarations are silently shifted to the top of their respective scopes during the compilation phase (so before execution begins). But here's the plot twist: for some of them, the initializations stay grounded! โฌ‡๏ธ ๐Ÿ˜ฒ

We will look at the nuanced ways JavaScript deals with hoisting and focus on:

  • function declarations,

  • variables declared with var, and

  • the somewhat mysterious behavior of let and const.

Time to elevate our understanding! ๐Ÿš€

But before we dive any deeper, let's talk about scope.

What is Scope? ๐Ÿ–๏ธ


Think of scope as your own private beach - only the variables declared there can have access and play!

In JavaScript, scopes can be:

โœ… Block Scope: the region of code within a pair of curly braces { }.

Variables declared with let and const are block-scoped, meaning they can only be accessed within that block in which they are declared.

if (true) {
  let blockedCat = "It's cozy in here!";
  const blockedDog = "I am stuck in here!";
} 

// blockedCat and blockedDog are not accessible outside this block
Enter fullscreen mode Exit fullscreen mode


โœ… Function Scope: the region of code within a function definition.

Variables declared with var inside a function are function-scoped, meaning they are only accessible within that function.

// Here captured is not accessible outside this function

function selfishFunction() {
  var captured = "Get me out of here!";
}

Enter fullscreen mode Exit fullscreen mode


โœ… Module Scope: the entire module has its own scope if you're coding using ES6 modules.

This means that variables declared in a module are only accessible within that module unless they are explicitly exported.


โœ… Global Scope: Variables declared outside of any function or block are globally scoped. These variables can be accessed and altered from any other scope in the program.

The Drama of Hoisting in JavaScript


Everyone is getting an early invite to the party but not everyone is being allowed to enter just yet!

In JavaScript, both function declarations and variable declarations are hoisted, but their behavior varies due to the JavaScript engine's parsing rules.

1. Function Declarations

Traditional function declarations - using the function keyword - enjoy VIP status; they are hoisted along with their definitions. This means you can call such a function BEFORE its code even appears, and it will execute flawlessly. ๐Ÿน ๐Ÿ˜Ž

Example:

hoistedFunction()
// Output: "This function has been hoisted."

function hoistedFunction() {
    console.log("This function has been hoisted.")
}
Enter fullscreen mode Exit fullscreen mode

Thanks to hoisting, you can structure your code by placing the more important logic at the top and the helper functions at the bottom, even if those helper functions are used in the upper parts of your code.

Note: there are subtle nuances when these declarations appear inside conditional blocks. For a closer look at how different JavaScript environments handle function declarations inside conditional blocks, please refer to the Demystifying Function Declarations Within Conditional Blocks post in this series.

2. Variable Declarations Using var

Variables declared with var do get hoisted but without their initializations (where you assign a value to a variable). Should you access such a variable before its definition, you'll receive undefined.

console.log(myNumber) // Output: undefined

var myNumber = 5      // variable is now assigned a value
Enter fullscreen mode Exit fullscreen mode

In the code above, the var myNumber declaration is hoisted to the top of the scope, but its initialization with the value 5 is not. This is why it returns undefined when we try to log it before actually defining it.

However, in the example above, we are fortunate: the console displays undefined, silently whispering to us that a variable is being accessed before its assignment.

What do you think happens when the uninitialized variable is part of a calculation or logical expression? Possible mayhem! Because it can lead to obscure and harder-to-detect issues.

Example:

var result = 2 * myVar // Output: NaN

var myVar = 7;
Enter fullscreen mode Exit fullscreen mode

In this scenario, result will be NaN because myVar is undefined at the time of the multiplication, and any arithmetic operation involving undefined results in NaN. These types of subtle bugs can be especially tricky to debug as they donโ€™t throw an error and might not become apparent until much later in the programโ€™s execution.

3. Variable Declarations Using let or const

Variables declared with let or const exhibit unique scope behavior. Although technically hoisted, attempting to access them prematurely leads to a ReferenceError rather than undefined.

The ultimate effect on your code is crucial: encountering undefined will not interrupt code execution, but it may lead to logical errors or unexpected behavior. On the other hand, encountering a ReferenceError will stop execution.

We call this space from the start of the block until the line where the variable is actually declared the Temporal Dead Zone (TDZ). ๐Ÿšซ

Example:

{  // Start of the block scope

console.log(boo) 
// Output: ReferenceError: boo is not defined

let boo = "ghost"
// boo is declared

console.log(boo) 
// Output: "ghost"

}  // End of the block scope
Enter fullscreen mode Exit fullscreen mode

In this example, the scope of boo is the entire block (the code within the { }). The TDZ for boo is the part of this block before the let boo = "ghost" line. Once we pass that line of code, boo is out of the TDZ and ready to be used.

Many developers prefer using let and const because a ReferenceError immediately alerts them to a scoping issue, halting the code and thus making it easier to debug. This behavior reduces the risk of logical errors that could arise when code execution continues with an undefined value, as is the case with var.

Note: For an in-depth look at the Temporal Dead Zone, please check out the second post from this series. Link at the end of the post.

4. What about Function Expressions?

It's important to note that function expressions - defined with var, let, or const - fall into this category as well.

Example:

console.log(myFunc)  
// Output: ReferenceError: myFunc is not defined

const myFunc = function() {
    console.log("myFunc is hoisted.")
}
Enter fullscreen mode Exit fullscreen mode

Unlike traditional function declarations, function expressions do not enjoy VIP hoisting privileges, as the example above illustrates. Just the dread of the TDZ!

Hoisting in Other Languages

While the notion of scope and lifetime of variables exists in virtually all programming languages, can we say the same thing about hoisting? Not at all! The concept of hoisting as it exists in JavaScript is not very common in other programming languages.

Most languages do not need a crane operator because they have a more straightforward scope and declaration system, where the order in which variables and functions are declared matters explicitly.

Languages like C, C++, Java, and Python do not hoist variables and functions in the way JavaScript does. In these languages, trying to access a variable before it has been declared usually results in a compile-time or run-time error. Therefore, developers have to be more deliberate about the order in which they declare and use variables and functions.

Conclusion

In JavaScript, hoisting behavior allows for more flexibility (and sometimes more confusion) in how you can order your code. When choosing how to define a function and what types of variables to use, remember the mechanics of hoisting!

But, the story continues! Two related posts:

The Temporal Dead Zone

Demystifying Function Declarations Within Conditional Blocks

Resources

JavaScript: The Definitive Guide
7th Edition, by David Flanagan
O'Reilly Media, 2020


Post originally published August 26, 2023 on corinamurg.dev

Credit: Photo by Gunnar Ridderstrรถm on Unsplash

Top comments (5)

Collapse
 
jodoesgit profile image
Jo • Edited

Thanks Corina, this was a fun read. Some of it was review, but some of it held more depth than I could state. Unknown knowledge, actively practiced - if you catch my drift. Was good to get into my head.

Collapse
 
corinamurg profile image
Corina: Web for Everyone • Edited

Thank you Jo! Happy you enjoyed it. I was struggling with the idea of hoisting for a while, so researching and writing the three related posts made things easier to understand.

Collapse
 
jodoesgit profile image
Jo

Oh absolutely! I see you're a former teacher. Once a teacher, always a teacher. Right? You have a very pleasant way of expressing things. It's concise and clear and I'd imagine it comes from your years of teaching. I will say summarizing learned information has also helped me in absorbing ideas. Practice of course helps, but translating that into some sort of communicated idea to others has been the solidifier. Which I've heard of before, but hadn't practiced much.

I'm going to look at the other posts I've seen at my leisure, but I'm going to seek them out for sure. Cheers!

Thread Thread
 
corinamurg profile image
Corina: Web for Everyone

Oh, that's so true! It took me a while to find my voice when writing about coding. Things clicked when I started to use my "teaching voice"! If I am curious about or struggling to understand a concept, I research it (a lot!) and then write about it. The first draft is never perfect, but I can always go back to it and refine it.

And thank you so much for your encouraging words! Just switching to this platform so I will transfer all my older posts and certainly write new ones. (You might see one almost every day lol)

Thread Thread
 
jodoesgit profile image
Jo

I'm a-okay with that. I'm new too. Found this place through Hacktoberfest. I'm pretty excited for your writings. It seems more accessible to have information in one consolidated place. Mind you, I'm not sure where you were before. You write so fantastically, you might want to look into writing some freeCodeCamp articles. I don't believe you get paid or anything for it, but a lot of people (including me) access it. Especially as you seem to focus on Javascript, and it leans heavily on web development.

Although I'm not entirely sure of the process. But I'm pretty sure it's just a search away.

As an aside, I'm not sure if you're a retired teacher by choice or by time. If it's by choice, and it was due to how they treat teachers I send condolences. You guys literally shape the future, and people treat you like hucky. I've got two friends in education, and they love (most) of their students. But they get so much flack from administration, the parents, and even some from the students. Likewise, it's hard to keep them engaged let alone working independently nowadays. It's a frustrating experience to see, but nothing I've got any skin or control over.

I will say I personally am worried about the future. I was doing a lot of urban living and have since moved to the West. Where the concept of urban living is sort of a joke unless you're talking California. I spent a lot of time around children during the tail end of the covid-times. Just in one of my apartment complexes. I saw them being setup for failure, by their parents and the system. As they were supposedly called back to be in school in-person and nobody went. But likewise, even when they were supposed to be in school remotely, nobody went. It was really...what's a good word to describe sad and terrifying? Because that's what it felt like.