Hoisting is one of the more confusing aspects of JavaScript. The concept of hoisting was created by developers to explain what happens during the compilation phase when variables and function declarations are moved — or hoisted — to the top of their containing scope. There are some good articles listed at the bottom of this post that explain JavaScript hoisting in detail. For me, the best explanation came from Kyle Simpson’s Advanced JavaScript course on Frontend Masters.
In the course, Kyle explains the compilation and execution phases of JavaScript in a psuedocode-esque conversation. This abstaction made the concept of hoisting click for me. I knew about hoisting before taking the course, but knowing and understanding are two very different things. Below is a code snippet along with a recap of the conversations that take place during the compilaton and execution phases.
var foo = "bar";
function bar() {
var foo = "baz";
function baz(foo) {
foo = "bam";
bam = "yay";
}
baz();
}
bar();
foo; // "bar"
bam; // "yay"
baz(); // Error!
JavaScript Compilation
The first step taken by the browser’s JavaScript engine
- Line 1: Hey global scope, I have a declaration for a variable named
foo
. - Line 3: Hey global scope, I have a declaration for a function1 named
bar
. - Line 4: Hey
bar
scope, I have a declaration for a variable namedfoo
. - Line 6: Hey
bar
scope, I have a declaration for a function namedbaz
. - Line 6: Hey
baz
scope, I have a declaration for a parameter namedfoo
.
- Since
bar
is a function, we recursively decent into its scope and continue compilation.
JavaScript Execution
The second step take by the browser’s JavaScript engine
There are two terms that you need to be familiar with as we enter the execution phase: LHS and RHS. LHS stands for left hand side, and RHS stands for right hand side. LHS references are located on the left hand side of the =
assignment operator. RHS references are located on the right hand size of the of the =
assignment operator, or implied when there is no LHS reference. If this seams a bit confusing, a good way to think about LHS versus RHS is target versus source. LHS is the target, and RHS is the source.
Let’s continue the conversation during the execution phase…
- Line 1: Hey global scope, I have an LHS reference for a variable named
foo
. Ever heard of it? - Line 13:1 Hey global scope, I have an RHS2 reference for a variable named
bar
. Ever heard of it? - Line 4: Hey
bar
scope, I have an LHS reference for a variable namedfoo
. Ever heard of it? - Line 10:4 Hey
bar
scope, I have an RHS reference for a variable namedbaz
. Ever heard of it? - Line 7: Hey
baz
scope, I have an LHS reference for a variable namedfoo
. Ever heard of it? - Line 8: Hey
baz
scope, I have an LHS reference for a variable namedbam
. Ever heard of it? - Line 8: Hey
bar
scope, I have an LHS reference for a variable namedbam
. Ever heard of it? - Line 8: Hey global scope, I have an LHS reference for a variable named
bam
. Ever heard of it? - Line 14: Hey global scope, I have an RHS reference for a variable named
foo
. Ever heard of it? - Line 15: Hey global scope, I have an RHS reference for a variable named
bam
. Ever heard of it? - Line 16: Hey global scope, I have an RHS reference for a variable named
baz
. Ever heard of it?
- Lines 3–11 don’t exist in the execution phase because they were compiled away. So, we move to line 13.
- The reason line 13 is an RHS is because there is no assignment. As such, we cannot establish a left/right reference. Therefore, we know that the value on line 13 represents the source.
- Within the
bar
scope,foo
will always refer to the value assigned to it on line 4. This is because thefoo
variable on line 4 is preceded with thevar
keyword, and will therefore be the first reference tofoo
inside thebar
scope. - Lines 6–9 don’t exist in the execution phase because they were compiled away. So, we move to line 10.
- If you are in strict mode, the
bam
variable will not be registered. Therefore, becausebam
doesn’t exist a reference error will be thrown. - Again, if you are in strict mode, a reference error will be thrown because
bam
doesn’t exist.
JavaScript Hoisting
Puting it all together — compilation + execution
Hoisting is simply a mental construct. You saw hoisting in action during the compilation phase in the example above. Understanding how JavaScript is compiled and executed is the key to understanding hoisting. Let’s go through one more simpler conversation in the context of hoisting and examine what happens with the code before, during and after compilation.
// Code as authored by developer
a;
b;
var a = b;
var b = 2;
b;
a;
/*
Notice: Variable declarations have been **hoisted** to the top of the
containing scope. In this case, the global scope.
*/
var a;
var b;
a;
b;
a = b;
b = 2;
b;
a;
/*
Notice: The var keyword has been compiled away.
*/
a; // undefined
b; // undefined
a = b;
b = 2;
b; // 2
a; // undefined
Now that you understand how variables get hoisted during the compilation phase, understanding the execution phase is much easier. Below is the conversation that occurs during the execution phase, as shown in the After Compilation figure above.
- Line 4: Hey global scope, I have an RHS reference for a variable named
a
. Ever heard of it? - Line 5: Hey global scope, I have an RHS reference for a variable named
b
. Ever heard of it? - Line 6: Hey global scope, I have an LHS reference for a variable named
a
. Ever heard of it? - Line 7: Hey global scope, I have an LHS reference for a variable named
b
. Ever heard of it? - Line 8: Hey global scope, I have an RHS reference for a variable named
b
. Ever heard of it? - Line 9: Hey global scope, I have an RHS reference for a variable named
a
. Ever heard of it?
To keep our example short and simple, I did not include functions. Note that functions are hoisted in the same way variables are. Functions get hoisted above variables during the compilation phase.
Hopefully this post helps to further your understanding of hoisting in JavaScript. Below are the resources on hoisting that I mentioned at the beginning of the post.
Comments