Hoisting in JavaScript

This behavior can surprise beginners to JavaScript, and also, I admit, people like me who learned JavaScript by doing.
So what is hoisting about? Concisely put, it means that every variable declared in a scope is actually declared when entering the scope, no matter where you choose to put the declaration.

In the following examples I’m going to create a function to define a scope, but this can be applied to every scope, the global scope, a scope you define with { and }, etc.

function foo() {
    var bar = true;
    foobar = "baz";
    var foobar;
}

Even in strict mode, this will work because it is equivalent to

function foo() {
    var bar,
        foobar;
    bar = true;
    foobar = "baz";
}

It is important to note that only declarations are hoisted, not initializations.

function foo() {
    console.log(bar); // undefined
    var bar = "foobar";
    console.log(bar); // "foobar"
}

That was what you would expect, but what about this example?

var foo = 50;
function bar() {
    var foo = foo + 1;
    console.log(foo); // NaN
}

This is, in fact, the same thing as

var foo = 50;
function bar () {
    var foo;
    // at this point `foo === undefined`
    foo = foo + 1; // and `undefined + 1` is NaN
    console.log(foo); // NaN
}

In order to prevent bugs because of this behavior, some developers such as Douglas Crockford recommend that “the var statement should be the first statement in the function body”. I believe this is a bit extreme and it makes the resulting code unorganized. I think it is better to be careful, knowing this rule. You decide.

var, function and function*

As we have just seen; before being initialized, a variable declared with var hold the value undefined.

It is worth noting that function declarations are hoisted, which means that they are not set to undefined.

function bar() {
    baz(); // "I am hoisted :D"
    function baz() {
        console.log("I am hoisted :D");
    }
}

Function expressions however, are not. They follow the same rules as everything declared with var.

function bar() {
    console.log(foo); // undefined
    var foo = function() {
    }
}
function bar() {
    var gen = foo();
    console.log(gen.next().value); // 0
    function* foo() {
        var i = 0;
        while (true) yield i++;
    }
}

Generator functions declarations behave like function declarations.

let, const and class

As we have seen this awful behavior leads to errors. To prevent them, the new ES6 keywords are subject to the TDZ (Temporal Dead Zone). Before the let, const or class statement, instead of being initialized to undefined, variables are actually not initialized.
Because something set to undefined may be initialized, remember? It’s just that it has the value undefined.

function bar() {
    console.log(foo); // ReferenceError: foo is not defined
    let foo = true;
}
function barbar() {
    console.log(foo); // ReferenceError: foo is not defined
    const foo = true;
}
function barbarbar() {
    baz = new Foo(); // ReferenceError: Foo is not defined
    class Foo {
    }
}

So if you try to access it, you get a ReferenceError, the same way you get an error accessing a variable you haven’t declared.

As a sidenote, anything defined with let, const and class is still hoisted.

Tags: JavaScript hoisting TDZ