Hoisting
Last updated
Last updated
In this lesson, we'll introduce the concept of hoisting, which deals with how function and variable declarations seem to get 'hoisted' to the top of the current scope. We'll also explain how the problems it causes are easily avoided by following simple rules for where and how declarations should happen within your code.
If you read any pre-ES2015 JavaScript materials, hoisting is sure to come up as a topic of concern. However, follow these two simple rules, and you'll never have to worry about it:
Declare all of your functions at the top of their scope. If the functions are declared in the global scope, simply put them at the top of the JavaScript file. If they're declared inside another function, put the declaration at the top of the function body.
Only use const
and let
. Never use var
.
Detail how function and variable declarations are 'hoisted'.
Explain why it's best to declare functions and variables (at least those declared with var
) at the top of the scope.
Understand, as always, that it's better to use const
and let
than var
.
Because the JavaScript engine reads a JavaScript file from top-to-bottom, it would make sense if we had to define a function before we invoked it:
However, we can invert those two steps and everything works fine:
NOTE: To follow along in your browser's JavaScript console, make sure you type all of the code into the prompt before you press Enter. To insert a new line without executing what you've typed, hold Shift and press Enter. If you type myFunc();
and then hit Enter, the browser will run your code, and you'll see an Uncaught ReferenceError
telling you that myFunc is not defined
. If it helps, you can copy and paste the above code all at once, or you can type it on a single line:
This reads as though we're invoking the function prior to declaring it, but we're forgetting about the two-phase nature of the JavaScript engine. During the compilation phase, the engine skips right over the invocation and stores the declared function in memory:
By the time the JavaScript engine reaches the execution phase, myFunc()
has already been created in memory. The engine starts over at the top of the code and begins executing it line-by-line:
The term for this process is hoisting because it feels a bit like your declarations are being hoisted to the top of the current scope. Your declarations are being evaluated before the rest of your code gets run, but hoisting is a bit of a misnomer: the physical location of the code isn't actually changing at all.
The best way to avoid any confusion brought on by function hoisting is to simply declare your functions at the very top of your code.
We're going to look at some of the hoisting issues caused by var
because you will encounter this weirdness in legacy code. However, the fix is extremely easy: use const
and let
and you'll have no variable hoisting issues.
Look at the following code:
Given what you know at this point, what do you think will be logged out to the JavaScript console when the code is executed?
It prints out undefined
. What the heck?!
You see, in JavaScript, hoisting only applies to variable declarations; not variable assignments. As a quick refresher on that terminology:
During the compilation phase, the JavaScript engine initializes the variable hello
, storing it in memory. At this point, however, no value is assigned to the variable. As far as the JavaScript engine is concerned, the variable hello
exists, but it contains undefined
.
The variable will contain undefined
until it's assigned a different value during the execution phase. Because of this odd behavior, you'll often see variable hoisting explained by taking some sample code...
and rearranging it to better indicate the order of events:
When rearranged, it's clear that the variable is initialized as undefined
, that it still contains undefined
when it's logged out to the console, and that only after the logging event is it assigned the value of 'World!'
. However, armed with knowledge of what's going on under the hood (the distinct compilation and execution phases), we don't need any of that code transposition nonsense. When we invoke the following function, five things happen:
The declaration of hello
(var hello
) is evaluated during the compilation phase, and the identifier, hello
, is stored in memory as undefined
.
The execution phase starts, and the JavaScript engine begins stepping through the code, executing each line in turn.
At the first line, console.log(hello);
, the value of hello
is still undefined
, and that's exactly what gets logged out to the console.
At the second line, the value 'World!'
is assigned to the variable hello
. From this point on, all references to hello
in this scope will evaluate to 'World!'
.
At the final line, we return
the value of hello
, which by now has been assigned and evaluates to 'World!'
.
var
hoistingThere are two ways to keep the JavaScript engine from 'hoisting' your variables:
If, for whatever reason, your current project requires that you use var
, follow our rule for function declarations and declare everything at the top of its scope. E.g., if you need to declare a variable within a function, declare it at the top of that function:
For the love of all things good in this world, don't use var
. Variables declared with const
and let
do technically get 'hoisted', but the JavaScript engine doesn't allow them to be referenced before they've been initialized. Bad:
Good:
Since we can't even reference them, the whole problem of hoisted variables evaluating to undefined
prior to assignment is moot.
Hoisting is often cited as an annoyance with JavaScript, but most of those complaints are from a pre-ES2015 world. Rejoice!