A Look at JavaScript and Declaration Hoisting

Just last week I did a long form talk on JavaScript functions at the Google Developers Group in Kitchener. As I was writing the examples I wanted to use, it occurred to me that there is a lot I could dive into — more than I could cover in the hour and a half I spent talking on the subject that night. So I thought it’d be a good idea to start blogging about JavaScript so I would, at the very least, have a backlog of short form talks for future presentations.

Today I want cover one of the fundamental and rarely understood topics in JavaScript that tends to be the root cause of misbehaved code; hoisting.

The subject as a whole is the start to so many other principles in JavaScript, like scope, inheritance, execution order, declarations vs expressions and so on. I’ll likely cover those topics later, but for now let’s look at what hoisting is and why it causes such difficulty to the unaware programmer.

What is hoisting? Hoisting the the common name given to an interesting mechanism in the JavaScript interpreter that “hoists”, or moves to the top, all variable and function declarations. It’s interesting in that it is an unseen behaviour yet it has very serious implications to how our code actually behaves.

Consider the following example:

(function () {
    var a = x();
    return a;
    function x() {
        return "I shouldn't be accessible";
    }
})();

We’d expect to return undefined since we ought to break out of the function execution prior to x() being defined. Instead we get the string “I shouldn’t be accessible”. So what’s actually happening here? This is where declaration hoisting occurs.

Unseen to the naked eye, the JavaScript interpreter is actually interpreting our function like this:

(function () {
    var a;
    function x() {
        return "I shouldn't be accessible. ";
    }
    a = x()
    return a;
})();

Wait, what? Let’s look at this another way:

(function () {
    var a = x();
    return a + y;
    function x() {
        return "I shouldn't be accessible. ";
    }
    var y = function () {
        return "I shouldn't be accessible either.";
    };
})();

In the above example we already know that we can in fact access x() but what about y()? Nope, y() returns undefined. Here is how it gets interpreted:

(function () {
    var a, y;
    function x() {
        return "I shouldn't be accessible. ";
    }
    a = x();
    return a + y;
    y = function () {
        return "I shouldn't be accessible either.";
    };
})();

So what is really going on here? Declarations and definitions are different when it comes to variables and the var keyword, but function declarations, as far as the function keyword is concerned, are one in the same with their definition. So in the case of our variables, var a and var y, their declarations get hoisted to the top while their definitions remain just where they were. The function keyword gets hoisted too, but its declared and defined in one fell swoop.

Ok, fine, why should we care? Because hoisting can ruin our day if we’re not aware of it and what it can do. Take the following example for instance:

(function () {
    function x() {
        return "I was here first.";
    }
    return x();
    function x() {
        return "It doesn't matter, I got hoisted.";
    }
})();

Most programmers would expect, even count on “I was here first.” being returned before x() was overridden, but that’s not what happens. The function is interpreted as such:

(function () {
    function x() {
        return "I was here first.";
    }
    function x() {
        return "It doesn't matter, I got hoisted.";
    }
    return x();
})();

So how do we steer clear of such issues? As programmers we should all be used to the idea of declaring our variables at the top of our function scope to begin with. The fact that variable declarations get hoisted shouldn’t really affect us. A lot of us will at least declare our variables with a default definition at the top of our function scope as well so there should be no surprises there either. So why not use function expressions instead of function declarations:

// function declaration
function foo() {
    return true;
}

// function expression
var foo = function () {
    return true;
};

Our previous examples would like so:

(function () {
    var x = function () {
        return "I am declared and defined! ";
    };
    var y = function () {
        return "And so am I!";
    };
    var a = x();
    return a + y;
})();

Which is ultimately interpreted as such:

(function () {
    var x,y,a;
    x = function () {
        return "I am declared and defined! ";
    };
    y = function () {
        return "And so am I!";
    };
    a = x();
    return a + y;
})();

Summary

As far as the JavaScript Interpreter is concerned, declarations and definitions are separate operations when it comes to the var keyword, but with functions they are on and the same. Only declarations get hoisted which can have unexpected results. Using function expressions over function declarations can help us keep our heads clear when hoisting occurs.

 

Adam Merrifield

 

Leave a Reply