JavaScript Hoisting Tutorial

In this section, we will learn what the hoisting is and how it works in JavaScript.

What is Hoisting in JavaScript?

In JavaScript, there are two steps that a source code goes through to be executed:

  1. Compiling.
  2. Execution.

The compile time is the time when variables are going declared in their scopes. This is also known as hoisting!

At this stage, no execution occurs, and just variables will be declared.

Note that no initialization happens in this stage, though! This means if there’s a variable with the name “fullName” for example, and there’s a value for this variable right at the declaration, it won’t be assigned to it until the execution time.

On the other hand, the execution time is the time when actual assignments and function executions happen.

At this stage, our program already registered the functions and variables with their scope and execution engine can simply look for them whenever it is needed.

Note: declaration is different from initialization. Declaration means registering the name of each variable in its scope, which is done at compile time. But if those variables have values to be assigned to them (AKA initialization), this is done in the execution time and not the compile time!

JavaScript Hoisting: Variables

Hoisting variables means at compile time, variables should be declared and be registered to their own scope so that at runtime (execution time) they could be accessed.

In the example below, we’ve explained how hoisting works with variables.

Example: hoisting variables in JavaScript

console.log(fullName);
var fullName = "John Doe"; 
console.log(fullName);

Output:

undefined

John Doe

As mentioned before, a source code goes through two stages:

  • Compile time (Hoisting).
  • Runtime (Execution time).

At compile time, the JavaScript compiler checks every statement in a program from top to bottom and tries to find variables and function identifiers to register them in their scope.

So here to begin with:

– The first statement is invoking a method name `log()` from `console` object! This has nothing to do with registering a new variable! So at compile time, this statement is ignored!

– The second statement is a variable declaration that is called `fullName`. So here, because we are at the global scope, the `fullName` variable will be registered in this scope.

Note that here the compiler only registers the `fullName` variable to the global scope! But it doesn’t assign its value! (Basically, it will only give it a default value which is `undefined`). And again, assignment and initialization are the processes that happen at runtime (execution time).

The last statement of this example is another call to the `console.log` method. Again, this is something that happens at runtime and not compile time! So it will be ignored until the execution time.

Basically, after finishing the compile time, our source code will be something like this:

console.log(fullName);

fullName = "John Doe";

console.log(fullName);

Note that there’s no `var` keyword anymore! Basically, the declaration part is gone now!

Alright, now that the compile time (hoisting) is done, it is time for the execution time:

The execution engine starts from the top of the program and looks for any method invocation or variable assignment, etc. at this stage.

– So the first statement in the source code that we got from the compile time is a call to the `console.log()` method. Here the execution engine looks for a variable named `fullName` in the global scope (because we’re at the global scope now) to get its value. But when the execution engine checks this scope, it sees the variable is declared, but it has the default value of `undefined`. So then the engine takes that `undefined` value and passes it to the `console.log()` method. That’s why we got the value `undefined` on the console the first time this method is invoked.

– The second statement in the source code is an assignment to the `fullName` variable. So here the engine again checks the global scope to see if there’s a variable named `fullName` registered in this scope at compile time (which there’s one) and then it will assign the value “John Doe” to this variable. Now the default value of this variable is replaced with this value.

– The third statement is another call to the `console.log()` method. This time again, the method is trying to access the value of `fullName` variable. So the engine will check the global scope to see if there’s a variable with this name and because there’s one, it will take its value (John Doe in this case) and passes it as the argument to the `log()` method. That’s why this time the call to this method returned the value of John Doe.

Note: at execution time, if there’s a call for a variable in order to assign a value to it, but there was no variable with that name registered in any scope, then JavaScript execution engine will create one automatically in the global scope and assigns a default value to it. For example, if we didn’t have the variable `fullName` at all, then the execution engine would’ve created one and assigned the value `undefined` to it.

Auto global variable creation only happens if the target variable is set on the left side of the assignment `=` operator. If we try to access a variable that didn’t exist to get its value and not assign a value to it, we will get a reference error instead.

JavaScript Hoisting: Functions

Hoisting a Function in JavaScript is pretty much the same as a variable. Basically, at compile time, any function will be registered in its scope (hoisted) so that at runtime they could be accessed.

Example: hoisting functions in JavaScript

var fullName = "John Doe"; 
function printName(){
  var fullName = "Omid Dehghan";
  console.log(fullName); 
}
printName();
console.log(fullName);

Output:

Omid Dehghan

John Doe

Alright, now let’s see the stages that this source code goes through to be executed:

  • Compile time (Hoisting):

– First, the compiler checks the first statement and sees a variable named `var fullName`. So here it will register this variable in the global scope because we are in this scope. Again, note that compilers won’t assign values to variables! This is done at the execution time.

– The second statement is a function declaration. So the compiler will register the `printName` identifier to the global scope and link the body of this function to this identifier. Note that the compiler won’t go into the body of this function until the execution engine sees a call to this function and then the same process will happen in the body of this function as well. Which means again the compiler will jump in and start to declare any variable in the body of this function as well and after that it will pass the control back to the execution engine.

But Let’s say for the sake of demonstration, the compiler goes into the body of this function now! So let’s see how the registration is done in this body:

– The compiler first sees a variable named `fullName` in the body of this function and so it will register this variable to the scope of the `printName()` function. Note that we’re in the scope of this function and so any declaration should be registered in this scope now.

– After that, there’s a call to the `console.log()` method, but because running this call is the job of the execution engine, the compiler simply ignores it and moves back to the global scope (because there are no other statements in the body of the function).

– The last statement in the global scope is another call to the `console.log()` method, but again, this is the job of the execution engine and so the compiler ignores this statement as well.

Alright, now the work of the compiler is done, and the compiled source code is passed to the execution engine to run!

fullName = "John Doe"; 
      function printName(){
        fullName = "Omid Dehghan";
        console.log(fullName); 
      }
      printName();
      console.log(fullName);
  • Execution time (Runtime):

– The first statement is a call to the `fullName` variable. So here the engine checks the global scope (Because we’re at the global scope) to see if there’s a variable with this name registered. Because there’s one, then the engine will pass the value `John Doe` as its argument.

– After that, there’s the `printName()` statement. So the engine will check the global scope again to see if there’s an identifier named `printName` in the global scope. Well, there’s one! So the engine will load the body of this function and will start to run the instructions in that body.

– Inside the body of the `printName()` function, there’s a call to the `fullName` variable. So here, because we’re at the `printName` scope, the engine will check this scope to see if there’s a variable with this name already registered or not! Now, because there’s one, then the engine will take that variable and assign the value `Omid Dehghan` to it.

– In the next statement, there’s a call to the `console.log(fullName)`. Here, this method needs the value of the `fullName` variable. So the engine starts with the `printName` scope to see if there’s a variable with this name registered or not. Because there’s one in this scope, it will use its value and pass it as the argument of the `log()` method. That’s how the value `Omid Dehghan` appeared on the console first.

Note: if the `printName` scope didn’t have the `fullName` variable, then the engine would’ve checked the global scope for this variable and if there was one, it would’ve taken its value as the argument for the `log()` method. But, if the global scope didn’t have this variable as well, then the engine would’ve returned an error as a result.

Alright, now back to the global scope, and the last statement is another call to the `console.log()` method.

Here the engine will check the global scope to see if there’s a variable with this name (which there’s one) and so it takes its current value (which is `John Doe`) and passes the value as the argument to the `log()` method. That’s how we got the value `John Doe` appeared on the console.

JavaScript Hoisting: Function Expression

Hoisting a function expression is pretty much the same as hoisting a variable! Because if you think about it, a function expression is just a variable that holds a function (which could be anything else of course).

So the process is the same.

Example: hoisting function expression in JavaScript

var fullName = "John Doe"; 
printName();
var printName = function (){
  var fullName = "Omid Dehghan";
  console.log(fullName); 
}
console.log(fullName);

Output:

Uncaught TypeError: printName is not a function

Note that we got an error this time!

Let’s see how this error occurred:

– At compile time, first the `fullName` variable is registered at the global scope. And after that, the variable `printName` will be registered as well.

Note that both of these variables have the value `undefined` as their default values. (Because compilers won’t assign values to variables! This is the job of the execution engine).

Now moving to the execution time:

The first statement in this program is the assignment to the `fullName` variable. So the engine checks the global scope to see if the variable is registered or not, and because there’s one with this name, then the value `John Doe` is assigned to it.

Now the second statement which is a call to the `printName()` function; here, the engine again checks the global scope to see if there’s a variable with this name registered or not. Now, because there’s one, the engine calls its value.

But here’s the problem! The default value of this `printName` is set to `undefined`! And basically because the value `undefined` is not a function! The engine returned the error you saw as the output of this program!

To solve this problem, all we needed to do was to call the `printName` function after its initialization at runtime.

Basically, if we change the source code above to the one below, the problem would’ve been solved:

var fullName = "John Doe"; 
var printName = function (){
  var fullName = "Omid Dehghan";
  console.log(fullName); 
}
printName();
console.log(fullName);

Note that this time the execution engine will see the `printName` variable and assign it the body of a function first and then, in the next statement, that function is invoked. This way we won’t get the error anymore.

JavaScript Hoisting: Arrow Functions

The process of hoisting an arrow function is pretty much the same as function expression and you just need to be careful when to declare and when to call (invoke) the function.

Basically, we first need to declare such function and after that invoke it whenever it’s necessary.

Example: hoisting arrow functions in JavaScript

var fullName = "John Doe"; 
 var printName = ()=>{
   var fullName = "Omid Dehghan";
   console.log(fullName); 
 }
 printName();
 console.log(fullName);

Output:

Omid Dehghan

John Doe

As you can see, here because we first declare the `printName` variable and then called it, we didn’t get any error. But if we change the place of invoking the `printName` function with its declaration, then the error will arise for the same reason you saw in the hoisting a function expression.

Note: There’s a concept called `Temporary Dead Zone (TDZ for short) ` that is related to the declaration of `let` variables. Please check the TDZ section to learn more about this. It’s an important topic!

Leave a Reply