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 Hoisting Example

JavaScript Compilation

The first step taken by the browser’s JavaScript engine

  1. Line 1: Hey global scope, I have a declaration for a variable named foo.
  2. Line 3: Hey global scope, I have a declaration for a function1 named bar.
  3. Line 4: Hey bar scope, I have a declaration for a variable named foo.
  4. Line 6: Hey bar scope, I have a declaration for a function named baz.
  5. Line 6: Hey baz scope, I have a declaration for a parameter named foo.

  1. 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?
The global scope has because foo was registered on line 1 in the compilation phase, so the assignment occurs.
Line 13:1 Hey global scope, I have an RHS2 reference for a variable named bar. Ever heard of it?
The global scope has because bar was registered as a function on line 3 in the compilation phase, so the function executes.
Line 4: Hey bar scope, I have an LHS reference for a variable named foo. Ever heard of it?
The bar scope has because foo was registered on line 1 in the compilation phase, so the the assignment occurs.3
Line 10:4 Hey bar scope, I have an RHS reference for a variable named baz. Ever heard of it?
The bar scope has because baz was registered as a function on line 6 in the compilation phase, so the function executes.
Line 7: Hey baz scope, I have an LHS reference for a variable named foo. Ever heard of it?
The baz scope has because foo was declared as a parameter of the baz function on line 6 in the compilation phase, so the assignment occurs.
Line 8: Hey baz scope, I have an LHS reference for a variable named bam. Ever heard of it?
The baz scope has not. Therefore we look for bam in the next outer scope, the bar scope.
Line 8: Hey bar scope, I have an LHS reference for a variable named bam. Ever heard of it?
The bar scope has not. Therefore we look for bam in the next outer scope, the global scope.
Line 8: Hey global scope, I have an LHS reference for a variable named bam. Ever heard of it?
The global scope has not. Therefore the global scope automatically registers a variable named bam.5
Line 14: Hey global scope, I have an RHS reference for a variable named foo. Ever heard of it?
The global scope has because foo was declared on line 1 in the compilation phase, its value is the string “bar”.
Line 15: Hey global scope, I have an RHS reference for a variable named bam. Ever heard of it?
The global scope has because bar was automatically created two steps back, its value is the string “yay”.6
Line 16: Hey global scope, I have an RHS reference for a variable named baz. Ever heard of it?
The global scope has not because baz was exists in the function scope of bar. Therefore, baz is inaccessible to the global scope and a reference error is thrown.

  1. Lines 3–11 don’t exist in the execution phase because they were compiled away. So, we move to line 13.
  2. 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.
  3. Within the bar scope, foo will always refer to the value assigned to it on line 4. This is because the foo variable on line 4 is preceded with the var keyword, and will therefore be the first reference to foo inside the bar scope.
  4. Lines 6–9 don’t exist in the execution phase because they were compiled away. So, we move to line 10.
  5. If you are in strict mode, the bam variable will not be registered. Therefore, because bam doesn’t exist a reference error will be thrown.
  6. 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;
Before Compilation
/*
    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;
During Compilation
/*
    Notice: The var keyword has been compiled away. 
*/
a;      // undefined
b;      // undefined
a = b;
b = 2;
b;      // 2
a;      // undefined
After Compilation

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?
The global scope has because a was registered on line 5 in the compilation phase, its value is undefined.
Line 5: Hey global scope, I have an RHS reference for a variable named b. Ever heard of it?
The global scope has because b was registered on line 6 in the compilation phase, its value is undefined.
Line 6: Hey global scope, I have an LHS reference for a variable named a. Ever heard of it?
The global scope has because a was registered on line 5 in the compilation phase, so the assignment occurs.
Line 7: Hey global scope, I have an LHS reference for a variable named b. Ever heard of it?
The global scope has because b was registered on line 6 in the compilation phase, so the assignment occurs.
Line 8: Hey global scope, I have an RHS reference for a variable named b. Ever heard of it?
The global scope has because b was registered on line 6 in the compilation phase and a value was assigned to it on line 7 in the [current] execution phase, its value is the number 2.
Line 9: Hey global scope, I have an RHS reference for a variable named a. Ever heard of it?
The global scope has because a was registered on line 5 in the compilation phase and b was assigned to it on line 6 in the [current] execution phase. As b was undefined at the time of its assignment to a, the value of a is undefined.

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.

Posted by: John Dugan

Comments

  • Pingback: Demystifying Debounce in JavaScript()

  • Juanfevasquez

    This is mind blowing! Thanks a lot!
    But… me and some of my colleagues became confused at the end of the post when you explained “a was registered on line 3 || b was registered on line 4”. Are you sure this happens on these lines?

  • Thanks much for the kind compliment! Apologies for things being unclear. When I added comments I screwed up the number references – oops! 🙂

    I updated the post. Let me know if things are more clear now.

  • Pingback: Lexical Scoping (hoisting) | Javascript Revisited()