Hoisting

Open the project folder

In our starter files, open the index.html page from this lessons folder:

09.Scope-Hoisting-Closures > 04.Hoisting

Starter file code

Earlier in this course, the term hoisting was mentioned, and we promised to come back to it. We have already seen some examples of hoisting already, let’s view some examples first to see what is happening.

The starter files have some examples, example 1 may be familiar. This was an example used earlier in the functions section:

// EXAMPLE 1
// =========
let bread = ['flour', 'yeast', 'salt', 'water'];
let brownies = ['butter', 'chocolate', 'flour', 'eggs', 'sugar'];

console.log(checkAllergies(bread, 'flour'));

// function declaration
function checkAllergies(recipe, ingredient) {
  const hasIngredient = recipe.includes(ingredient);
  return hasIngredient;
}
// function expression
// const checkAllergies = function (recipe, ingredient) {
//   const hasIngredient = recipe.includes(ingredient);
//   return hasIngredient;
// };

We have arrays with bread and brownies ingredients. Then the same function is created twice using a different syntax. There is the function declaration we have used mostly in the examples.

Then a function expression version which does the same thing, it will check if an ingredient is included in the array.

Why are we showing you this again? Well notice how we are calling the function with the console log before it is created.

Calling a function before creation

This works fine with the function declaration, and if you remember from earlier, we will get an error if we try this with the function expression version:

Uncaught ReferenceError: Cannot access 'checkAllergies' before initialization

Accessing a variable before declaration

Then we have example 2 which is a simplified version of the issue:

// EXAMPLE 2
// =========
console.log(name);
var name = 'Chris';

This works even though we access name before it is declared. It gets even more strange if we try to re-assign the name value before it is declared:

// EXAMPLE 2
// =========
name = 'Christopher';
console.log(name); // Christopher
var name = 'Chris';

Again, the log will print the updated example without any issues.

Although these examples are varied, they all show a fundamental thing. We can often access variables before they are declared. And this leads us to a term called hoisting.

Just quickly before we jump into this, I want to also show you something else, which is a variant of the last example:

// EXAMPLE 2
// =========
console.log(name);
var name = 'Chris';

console.log(role);
var role;
role = 'dev';

In the console you will see the name variable works, but we have undefined for the role. This may seem strange, since we have been looking at how we can access variables before they are declared, and since both examples are similar. To understand this, let's see what hoisting actually does.

Hoisting explained

Hoisting is often described as the process of JavaScript taking our variables and function declarations, and moving them to the top of the program, or the top of their scope. Meaning they are declared, and ready to access any time. This would explain a lot of what we have just seen.

Although this sounds like it would make sense, no code physically gets moved around. It is the process of our variables and function declarations being placed into memory at compile time.

So the human-readable JavaScript code we write can be read by a computer, it needs to be converted, or compiled behind the scenes, into a set of instructions which computers can understand. And it is during this compile phase, our code is read or parsed, and our variables and function declarations are then stored into memory.

There is no hidden magic, no moving our code, just our code being parsed, and a reference to our variables and function declarations stored into memory.

This explains a few things, from our examples:

  1. Example 1: Function declarations are hoisted, therefore we can call the function before it is declared. The function expression is not hoisted which is why we see an error.
  2. Example 2: The original example ( var name = ‘Chris’ ), this variable is hoisted, we can access it before declaration.

What is stored in memory?

An important part to understand is what exactly is stored. To better understand this, let’s take a quick step back to variable basics. Earlier we looked at some of these variable related keywords:

  1. Declaration: Create or "declare" a new variable. E.g. var name;
  2. Initialization: A value is assigned to a variable. E.g. var name="Homer";
  3. Assignment: Pass or "assign a value to a variable". E.g. var age=37;

A declaration is when we declare a variable we want to use and give it a name. Once we pass it an actual value, this is referred to as initialization. So, we have declaration, and initialisation.

Returning back to this example, the name variable works, but we have undefined for the role:

// EXAMPLE 2
// =========
console.log(name);
var name = 'Chris';

console.log(role);
var role;
role = 'dev';

For the role variable, we have declared an empty variable, then after this we initialised it with a value of dev.

At this early compile phase, only the empty variable declaration is hoisted and stored into memory. The following line where we pass it the value of dev is ignored at this stage, and the variable is assigned a value of undefined, which is what we see in the console.

It is also important to know that we are using the var keyword here too. This behaviour is different with let:

console.log(role);
let role;
role = 'dev';

This will give an error rather than undefined:

ReferenceError: Cannot access 'role' before initialization

A const or let declaration needs to be declared before we can access it. If we do not assign it a value, it will also be given a value of undefined, but we still must declare it before we try to access it in our code.

This can be a tricky subject to understand at the beginning, since const, let, and var all do hoist, but var does things differently, and is automatically initialised at the beginning of its scope.

But being aware of this behaviour can save you a lot of trouble, and a lot of time debugging if you run into any issues.