JavaScript Hoisting

JavaScript Hoisting

Originally posted on dev.

In this article, we are going to discuss about hoisting in JavaScript.

Hoisting, is a process that happens during the creation of execution context, which will move the declarations of variables and functions up to the top of the context. Let’s start with an example:

hello();

function hello(){
    return "Hello World";
}

If you are familiar with JavaScript, you know that this is not going to throw an error. What is happening when during the creation of global execution context is that the engine will move the variable declaration (in this case the function “hello”) up to the context when it sees “function” keyword. Which also means, before the code is executed, it will look something like this:

function hello(){
    return "Hello World";
}
hello();

It moves that declaration up. Therefore, memory is allocated to store this function declaration before the code execution. How about variables that is not a function declaration?

console.log(text)

var text = "Hello World!"

Let’s say I have this piece of code that tries to print out variable “text” before it gets declared and defined. What will it print out?

undefined

Undefined. When the engine sees “var”, it will know to leave a piece of memory for this variable, but it doesn’t know that what value has been assigned to this variable. Therefore, when the declaration of the variable is hoisted, “undefined” gets assigned to this variable as default. This is the reason why error was not thrown, but printed out “undefined” instead.

You have also seen this way of defining a function:

hello();

var hello = function(){
    return "Hello World";
}

This is a function expression. What if we do this? Are we going to get the “Hello World” printed out correctly?

VM592:1 Uncaught TypeError: hello is not a function
    at <anonymous>:1:1

Hmm… ok, so the engine does not know that this is a function. Remember? When the engine sees “var”, it is going to move the declaration of “hello” up and gives an undefined default value. If you try to call it, it is going to tell you that this is not a function. Let’s print out what hello is before assigning that function to it.

console.log(hello);

var hello = function(){
    return "Hello World";
}
undefined

Just now we mentioned that when the execution context is created, the variables declaration get hoisted. Let’s try this:

function hello(){
    console.log("1: " + text)
    var text = "Hello World"
    console.log("2: " + text)
}

hello()

What can we expect here?

1: undefined
2: Hello World

Alright, this is aligned with what we have just discussed. Let’s give a value to the “text” in global scope.

var text = "Am I printed?"
function hello(){
    console.log("1: " + text)
    var text = "Hello World"
    console.log("2: " + text)
}

hello()

Again, what will you expect it to print out?

1: undefined
2: Hello World

Still, 1: is undefined. When hello() is called, a new execution context is created, and “text” variable is hoisted to the top of that hello() context, therefore, it is not aware of the assigned value from global execution context.

Let’s try one more thing, we remove the declaration of variable within hello():

var text = "Am I printed?"
function hello(){
    console.log("1: " + text)
}

hello()
1: Am I printed?

Yes, you are printed. Scope chain, right? (JavaScript Scoping)

Do you think all these will cause confusion when we are writing JavaScript code?

In order to make our code more predictable, we can use “let” and “const” in ES6. They don’t get hoisted.

hello();

const hello = function(){
    return "Hello World";
}
Uncaught ReferenceError: hello is not defined
    at <anonymous>:1:1

Yup, this feels better. There are some unexpected behaviours that might happen when you just started learning JavaScript (like type coercion). However, understanding these concepts will definitely make you a better JavaScript developer.

Since we mentioned “let” and “const”, I would also like to add on one more regarding block scope and function scope. When we use other languages, this is something that we would expect to throw error (we use groovy here as example):

if(someCondition)
{
    def someVar = "Can you see me? "
}
def newVar = someVar + "I cannot see you."

This is because the variable is defined within the “block scope”. What if we do this in JavaScript?

var someCondition = true

if(someCondition)
{
    var hello = "Hello "
}

var helloWorld = hello + "World"

console.log(helloWorld)
Hello World

Error will not be thrown in this case for JavaScript. This is because “var” is only scoped by function and module. If you do this in a function, it will throw error.

function someFunction()
{
    var hello = "Hello "
}

var helloWorld = hello + "World"

console.log(helloWorld)
VM1525:6 Uncaught ReferenceError: hello is not defined
    at <anonymous>:6:18

What if we also want to have block scope for {}?

Use “let” and “const”.

let someCondition = true

if(someCondition)
{
    let hello = "Hello "
}

let helloWorld = hello + "World"

console.log(helloWorld)
VM1566:8 Uncaught ReferenceError: hello is not defined
    at <anonymous>:8:18

That’s it for this article, hope you enjoyed!

 

Source: dev