Understand Hoisting In Javascript

Understand Hoisting In Javascript

What is Hoisting ?

During compile phase, just seconds before your code is executed, it is scanned for function and variable declarations. All these functions and variable declarations are added to the memory inside a JavaScript data structure called Lexical Environment. So that they can be used even before they are actually declared in the source code.

To note, hositing mechanism only moves the declaration. Assignement are left in place.

What is lexical environment ?

A lexical environment is a data structure that holds identifier-variable mapping. (here identifier refers to the name of variables/functions, and the variable is the reference to actual object [including function object] or primitive value).

This is what a lexical environment conceptually look like:

So in short, a lexical environment is a place where variables and functions live during the program execution.

Now that we know what hoisting actually is, let’s take a look at how hoisting happens for function and variable (var, let and const) declarations.

Hoisting Variable Declaration.

Javascript engine moves the variable declarations to the top of the script. Lets look at an example of to understand hositing with var.

console.log(x); //undefined
var x = 10;

However, the output of the above code would be undefined, the reason being javascript moves the variable declaration to the top of the scope. Following is the code in the execution phase.

var x;

console.log(x)
x = 10;

But why undefined?

When JavaScript engine finds a var variable declaration during the compile phase, it will add that variable to the lexical environment and initialize it with undefined and later during the execution when it reaches the line where the actual assignment is done in the code, it will assign that value to the variable.

So the initial lexical environment for the above code will look something like this:

lexicalEnvironment = {
  x: undefined
}

That’s why we got undefined instead of 10. And when the engine reaches the line (during execution) where the actual assignment is done, it will update the value of the variable in its lexical environment. So the lexical environment after the assignment will look like this:

lexicalEnvironment = {
  a: 10
}

Hoisting Function Declaration:

helloWorld();  // prints 'Hello World!' to the consolefunction 
helloWorld(){
  console.log('Hello World!');
}

As we already know that function declarations are added to the memory during the compile stage, so we are able to access it in our code before the actual function declaration.

So the lexical environment for the above code will look something like this:

lexicalEnvironment = {
  helloWorld: < func >
}

So when the JavaScript engine encounters a call to helloWorld(), it will look into the lexical environment, finds the function and will be able to execute it.

Hoisting Class Declaration

classes in JavaScript are also hoisted, they remain uninitialized until evaluation. So they are also affected by the “Temporal Dead Zone”. It means you can’t access the class before the engine evaluates its value at the place it was declared in the source code. A time span between class creation and its initialization where they can’t be accessed. For example:

let car1 = new Car('tesla', 'red); // ReferenceError: car1 is not defined
console.log(car1);

class Car{
  constructor(name, color) {
    this.name = name;
    this.color= color;
  }
}

So to access the classes, you have to declare them first. For example:

class Car{
  constructor(name, color) {
    this.name = name;
    this.color= color;
  }
}

let car1 = new Car('tesla', 'red);
console.log(car1);
// Car{ name: 'Tesla', color:'red' }

So again during the compile phase, the lexical environment for the above code will look like this:

lexicalEnvironment = {
  Car: <uninitialized>
}

And when the engine has evaluated the class statement, it will initialize the class with the value.

lexicalEnvironment = {
  Car: <Car object>
}

Hoisting let and const ``variables:

Let’s first take a look at some examples:

console.log(x);
let x = 3;

Output:

ReferenceError: x is not defined

So are let and const variables not hoisted?

All declarations (function, var, let, const and class) are hoisted in JavaScript, while the var declarations are initialized with undefined, but let and const declarations remain uninitialized.

They will only get initialized when their lexical binding (assignment) is evaluated during runtime by the JavaScript engine. This means let and const are also affected by “Temporal Dead Zone”.

If the JavaScript engine still can’t find the value of let or const variables at the line where they were declared, it will assign them the value of undefined or return an error (in case of const).

Let’s look at some more example:

let x;
console.log(x); // outputs undefined
x = 3;

Here during the compile phase, the JavaScript engine encounters the variable x and stores it in the lexical environment, but because it’s a let variable, the engine does not initialize it with any value. So during the compile phase, the lexical environment will look like this:

lexicalEnvironment = {
  x: <uninitialized>
}

Now if we try to access the variable before it is declared, the JavaScript engine will try to fetch the value of the variable from the lexical environment, because the variable is uninitialized, it will throw a reference error.

During the execution, when the engine reaches the line where the variable was declared, it will try to evaluate its binding (value), because the variable has no value associated with it, it will assign it undefined.

So the lexical environment will look like this after execution of the first line:

lexicalEnvironment = {
  x: undefined
}

And undefined will be logged to the console and after that 3 will be assigned to it and the lexical environment will be updated to contain the value of x to 3 from undefined.

Note — We can reference the let and const variables in the code (eg. function body ) even before they are declared, as long as that code is not executed before the variable declaration.

For example, This code is perfectly valid.

function foo () {
  console.log(a);
}let a = 20;
foo();  // This is perfectly valid

But this will generate a reference error.

function foo() {
 console.log(a); // ReferenceError: a is not defined
}
foo(); // This is not valid
let a = 20;

Conclusion

Hoisting our code is not physically moved by the JavaScript engine but it only moves the declaration and keeps the assignment as it is. understanding of the hoisting mechanism will help you avoid any future bugs and confusion arising due to hoisting.

Avoid side effects of hoisting like undefined variables or reference error, by declaring the variables at the top of their respective scopes and also always try to initialize variables when you declare them.

If you found this article helpful, please click the like 👍 button below to show your support and if you have any doubt, feel free to comment below! I’d be happy to help😃