loading...

Hoist Your Vars! (Variable Hoisting in JavaScript)

thecodepixi profile image Emily A. Pixi ・3 min read

At its core, hoisting is an "order of operations" issue. JavaScript code goes through two phases: compilation and execution.

  • Declarations (var, let, const, and function) are read first during code compiling.
  • Assignments (thing = value) and function calls (someFunction()) are read second during execution.

The main difference between the var, let, and const declarations is the way they are/can be initialized. var and let can be initialized without a variable, or without pointing to any value. Attempting to initialize const without a value will throw a reference error.

You can declare var and let variables anywhere in your code, and then assign them anywhere else. With const you must declare and assign a value at the same time.

During the compiling phase, variable declarations are hoisted to the top of the code, below function declarations, and above everything else.

Some example code:

    console.log(thisVar)
    var thisVar = "Hoisted" 

    // compiles to: 
    var thisVar
    console.log(thisVar)
    thisVar = "Hoisted"

If you were to try to run this piece of code, this would be your result:

    console.log(thisVar)
    var thisVar = "Hoisted"

    //OUTPUT: 
    > undefined

The var thisVar declaration is read, but the assignment comes after the function call (or console.log(), in this case), which causes the result to be undefined, because the program knows that the variable exists, but at the time of the console.log() does not yet know what value it points to.

Another significant part of hoisting is the ability to call a function before it has been declared in your code.

As mentioned earlier, both var variables and function declarations are read first during compiling. Function calls are only read/run during the execution phase. Due to this code processing order, we can do things like this:

    belowCall()

    function belowCall(){
        console.log("I was called before I was declared!")
    }

    //OUTPUT:
    > undefined
    > I was called before I was declared!

Why does this work? Because during compiling phase, function calls are essentially invisible. The compiling phase skips over all function calls, reads for what code to execute when they are called, and then the calls are read and run during execution phase.

However, if you were to try this with a variable pointing to your function (a function expression), you'll run into trouble:

    varFunction();

    var varFunction = function(){
        console.log("I was called before I was assigned!")
    }

    //OUTPUT:
    > TypeError: varFunction is not a function

What the heck!? Here's what the heck:

    // How the compiler reads the code above: 

    var varFunction; 

    varFunction(); 

    varFunction = function(){
        console.log("I was called before I was assigned!")
    }

Remember! Variable assignment is read during execution phase, but after function calls.

What's happening above is that we are telling our code that we have a var declaration called varFunction, we attempt to call varFunction(), and then we tell varFunction what it points to (a function).

At the time the code is run, our JavaScript program doesn't yet know that varFunction is a function expression, only that it's a variable that exists. So rather than coming back as undefined like our previous var declarations, JavaScript goes "Hey, you told me to call this function but you haven't told me about it yet, so I'm mad at you!"

OK. So, maybe it's var's fault? Let's try using let instead...

    thisFunction();

    let thisFunction = function(){
        console.log("I was also called before I was assigned!")
    }

    //OUTPUT: 
    > ReferenceError: can't access lexical declaration `thisFunction' before initialization

That doesn't work either!

This is at least a little more helpful, though, since the error Javascript is giving us is pretty much saying "Hey, it looks like you put some stuff in the wrong order". You still cannot assign a function expression after calling it, but when using let your error message at least provides a little bit more insight.

( As a side note, this is the same error you would get when attempting to utilize any other let variable in your code before it has been assigned AND is the same error you'll receive if you try to do something similar with a const declaration/assignment )

Important Takeaways:

NEVER use var declarations. Just...don't do it. They will wreak havoc, and have been replaced and outmoded by the much improved let and const declarations.

Remember the compilation/execution order: Function declarations > variable declarations > function calls > variable assignment. This order helps give you a sense of what will be hoisted where in your code during the compiling and execution phases.

Posted on by:

thecodepixi profile

Emily A. Pixi

@thecodepixi

Founder and Organizer of Code Cafe Online. Flatiron School Software Engineering grad. Learner Advocate and Production Engineer with egghead.io

Discussion

pic
Editor guide
 

I like to use this in try/catch statements as well as some if cases

some times you just want to go const all the way but when you meet these cases using let doesn't feel attractive and in the end, you still need to declare the let variable.
Example:

let result;
try {
  result = "abc" // await getResult();
} catch (err) {
  result = "catch"
}
console.log(result)
// or return result
try {
  var result = "abc" // await getResult()
  // throw new Error('I failed you u.u')
} catch (err) {
  result = "catch"
}
console.log(result)

As with anything in code, as long as you and your team agree to do certain things it's okay. If you don't feel is right or your team doesn't want to go that road you can keep it for yourself

 

I am curious, why is your variable being mutated in the catch block? that block is for errors

 

A good example of when you might want to do this is if it’s a known exception and you are going to hand wave it away.

(async () => {
let result
try {
  // catch network issues
  const response = await fetch('https://example.com')
  // catch non-json data
  const data = await response.json()
  // catch inaccessible object
  result = data.messages
} catch(e) {
  result = { messages: [] }
}
})().then().catch()
 

the times I need to do this is to show a default for something
or just ensure there is a valid value returned from the function, most of the time this is used on places where failure is not critical or it's a "top level" UI element function call

I'd say it's kind of fail behind scenes keep the good face up

 

Thanks for Hoist explanation, but as a newbie I'm a little confused about your statement of "NEVER use var".
Since now, I thought that main differences between var and let/const lay in the scope field and they visibility in current execution context.
What havoc exactly var statement could wreak? In case of hoisting - is it better not to avoid var, but to avoid havoc in code by assuming that JS is single thread execution scripting language?

And another question that I have after reading - the words "compilation phase". Do we really have compilation process in scripting language?

 

Your questions are addressing too many things. You are talking about scope, execution context, thread, compilation. These are things that you must understand fully. I think that you would benefit from watching this video. please pay for the full course, it will greatly improve your JavaScript skills. Tony teaches about all these things.

youtube.com/watch?v=Bv_5Zv5c-Ts

 

While it is important to understand hoisting in JavaScript, it is also a bad practice to use it in general. I suggest that you treat JavaScript just like any other programming language and don't use hoisting.

I have a template that I follow for any script that write:
// Declare variables:

// Declare functions:

// Start of coding:

Having those three sections makes it much easier for me to keep track of what is in my script.

 

That is excellent advice and very true!

 

so many people are confused about what hoisting is. Let me start off by bursting the bubble with one statement

All declarations are hoisted (var, let, const, function, class)

 

You're absolutely right and that's a point I didn't make clearly enough in the article. Might merit an edit.

 

Super helpful! No more vars for me :P

 

Wow! Great summary and explanation of a complex and esoteric topic!