Table of Contents
One of the quirkiest things about JavaScript is this thing known as hoisting. We'll get to what it means in a bit, but let's set the stage for it by looking at some examples and figuring out what the right behavior should be. For our first example, take a look at the following code:
function foo() {
return "Yay!";
}
console.log(foo());
What do you think is going to be displayed in our console when this code runs? Here are three choices:
If you guessed Yay!, you would be right. There wasn't anything tricky there, so that was pretty straightforward. Let's kick things up a notch and take a look at a slightly modified version of our earlier code:
console.log(foo());
function foo() {
return "Yay!";
}
We are logging the value returned by our foo function before we actually even defined it. What do you think is going to be displayed in the console now? Will it be the same as before? Will it be something different?!! As it turns out, the answer is the same as before. Our console will print out Yay! as the output. Hmm...
It's time for our last example. Take a look at the following code:
console.log(bar);
let bar = 100;
This seems similar to what we've seen so far, right? You would expect the value 100 to be printed to the console. The actual answer is a whopping undefined. What is going on here? By the end of this article, you will know all about what's happening and the role this hoisting thing plays.
Onwards!
When you are running JavaScript, you have a JavaScript engine that takes the code you write and turns (aka compiles) it into the stuff that your computer understands and knows what to do with. As part of turning your code into something your computer understands, the compiler (aka the thing that does the turning) performs a variety of steps. One of these steps has to do with what happens when our compiler runs into any variable and function declarations. Depending on whether the declaration is for a variable or a function, the behavior is a little different. Let's look at each case separately.
Whenever our compiler encounters a block of JavaScript, the first thing it does is scan the entire block for any variable or function declarations. Take a look at the following example we saw earlier:
console.log(bar);
let bar = 100;
Our compiles looks at both of these lines, but before anything gets executed, it hones in on the variable declaration where we declare the bar variable:
console.log(bar);
let bar = 100;
At this point, what it does is promote the variable declaration to the beginning of the scope it is looking at. From the compiler's point of view, our code will look a bit like this:
let bar;
console.log(bar);
bar = 100;
This promotion of the declared variable is known as variable hoisting. The important thing to note is that only the declaration is hoisted. The initialization where we set the bar's value to 100 remains in the exact same spot. That explains what was going on with our example earlier and why we were printing an undefined. Because of hoisting, the bar variable exists when we try to log it. Because the variable isn't initialized at this point, what gets logged is a value of undefined.
The behavior you saw with variable declarations is similar for functions as well. The major difference is that the entire function is hoisted - not an empty shell of it. Let's revisit our earlier example:
console.log(foo());
function foo() {
return "Yay!";
}
When our compiler scans this block of code, it hoists the foo function to the top. This is known as function hoisting. What you have is something that ends up looking as follows:
function foo() {
return "Yay!";
}
console.log(foo());
That is why the output for this example is Yay! just as it would be if we wrote our code with the function definition specified before we try to call it. Pretty simple, right?
We are almost done here. Just because we understand how hoisting works doesn't mean there aren't some exceptions to what we've seen.
First, hoisting doesn't apply to function expressions. Look at the following example:
console.log(foo());
let foo = function() {
return "Yay!";
}
The output isn't going to be Yay! like we saw with plain old functions earlier. It is going to be a TypeError: foo is not a function.
There are cases where hoisting applies but the value is never initialized to something like undefined as we've seen with var. Those cases occur when you are working with let, const, and class. Take a look a the following block of code:
console.log(answer) // ReferenceError
let answer = "Correct";
console.log(ROLE) // ReferenceError
const ROLE = "user";
let foo = new AwesomeSauce(); // ReferenceError
class AwesomeSauce {
constructor() {
console.log("I exist!");
}
}
In all these cases, we would expect the variable or class to exist but simply not be initialized. That isn't the case. What you get with each example is a ReferenceError. It doesn't mean that using classes or variables defined using let or const don't get hoisted. They certainly do! The difference is that they remain uninitialized. This time between them getting declared and initialized has a pretty awesome name - the temporal dead zone.
For the longest time, we've always been told to declare our variables and functions first before initializing them. We've also been told to only use a variable or function after it has been initialized. None of that guidance changes. Just because JavaScript has a behavior around hoisting declarations to the top of the current scope doesn't mean we should make our code more difficult to read by relying on it. Think of hoisting as a warning for you to fix your code. It isn't intended to be something you look forward to using :P
The original version of this article forgot to mention the behavior with const and let. It also mentioned the hoisting behavior as an optimization as opposed to a behavior ingrained into how JavaScript works. Thanks to krilnon and senocular for pointing both of these issues out.
Just a final word before we wrap up. If you have a question and/or want to be part of a friendly, collaborative community of over 220k other developers like yourself, post on the forums for a quick response!
:: Copyright KIRUPA 2024 //--