AddThis

Monday, June 2, 2014

JavaScript Hoisting

Today I'm going to spend a little bit of time on hoisting in JavaScript.  A very cool and useful feature if you know how it works, but painful and confusing if you don't.

So what is hoisting?  Hoisting is JavaScript's behavior of moving declarations to the top of your scope (typically either the top of the global scope or function if you are in one).

There are two main types of things that can get hoisted, variables and functions.  We will go through each one of these presenting example cases to help illustrate how they work.  And at the end we will put everything together with a short quiz that contain aspects of everything discussed here.


Variable Hoisting

Case 1: Using a variable that doesn't exist.

console.log(name);

If you run this script, you get

ReferenceError: name is not defined.

This makes sense.  The variable name wasn't defined anywhere and as such, our JS engine can't resolve it and blows up.

Case 2: Correctly defining a variable and using it.

var name = 'Jon';
console.log(name);

This declares and defines name, and then uses it.  This works as expected and you will see 'Jon' written out to the console.

Case 3: Defining name after usage.

console.log(name);
var name = 'Jon';

You would expect to see the same behavior as Case 1, a ReferenceError, but what you see instead is 'undefined' written out.  This is because of hoisting, in this case, variable hoisting.  The declaration of the name variable was hoisted to the top of this scope.  Notice I said declaration, not definition.  The definition stays right where it is.  This is why name is undefined when it gets printed out.  Let me rewrite what the JS engine actually executes after hoisting.

var name;  //gets the default value of undefined
console.log(name);
name = 'Jon';

As you can see, now the output makes sense.
What other things can get hoisted?  In the next section, I'll talk about function hoisting.


Function Hoisting

Just like variable declarations, function declarations can get hoisted too.  But fun bit here, since the declaration and definition are done together, everything gets hoisted.

Case 1:  Using a function that doesn't exist.

sayHi();

This gives us the same result as Case 1 above,

ReferenceError: sayHi is not defined.

This makes sense.  You can't use something that you haven't declared.

Case 2: Using a function correctly defined after we declare and define it.

function sayHi() {
  console.log('hi');
}
sayHi();

Output here is 'hi'.  We declare and define a function sayHi, and then invoke it.  Everything works and this code is easy to understand.

Case 3: Using a function before we declare and define it.

sayHi();
function sayHi() {
  console.log('hi');
}


The output here is the same as Case 2, we get 'hi' printed out.  This is because the entire sayHi function is hoisted to the top.  So when we invoke sayHi(), it already exists.  Let's take a look at the code after hoisting.

function sayHi() {
  console.log('hi');
}
sayHi();

Notice that it's exactly the same as Case 2.


Hoisting of a Variable Holding a Function

What about this case, if we have a variable that holds a function.  That function is typically anonymous (it doesn't have to be) and we often see it as such:

var sayHi = function() {
  console.log('hi');
}

How does this get hoisted?

Case 1: Using a function that doesn't exist.

sayHi();

This looks like Case 1 above and is.  It gives us the same result,

ReferenceError: sayHi is not defined.

Same reason as before, you can't call something that isn't declared.

Case 2: Using the function after we declare and define it.

var sayHi = function() {
  console.log('hi');
}
sayHi();

This works as expected and prints out 'hi' into the console.  We've properly defined an anonymous function, set it to a variable, and then later invoked it.

Case 3: Using the function before we declare and define it.

sayHi();
var sayHi = function() {
  console.log('hi');
}

Boom!  We get

TypeError: undefined is not a function. 

What happened here?  Why didn't hoisting save us?  It saved us for both variables and functions, why not this time?  It's very subtle, but if you apply the hoisting rules you have learned thus far, you can see why this failed.  Let's rewrite the code after hoisting is applied.

var sayHi;  //set to default value of undefined
sayHi();
sayHi = function() {
  console.log('hi');
}

Because sayHi is actually a variable here, the variable hoisting rule applies.  The JS engine declared the variable at the top of the scope, and left the definition where it was.  So at the time we try and invoke sayHi, it's actually set to undefined.  Obviously, undefined is not a function, which is what our exception complained about.

An important thing to remember here is that for variables, only the declaration gets hoisted, while as for a function, the whole thing gets hoisted.  So this has implications in that when a function gets hoisted, it's immediately accessable/invocable anywhere within that scope.  But if you have a variable containing a method, you can't actually invoke that method until the definition of that method.  Up until that point, the variable will hold the value 'undefined' which you have already seen.

Now let's put it all together.  Look at the code below and see if you can figure it out before I reveal the answer.


The Quiz

Taking everything you learned, what gets executed here?

var f = function() {
    console.log("Me original.");
}
function f() {
    console.log("Me duplicate.");
}
f();




























Answer just below....









Answer

If you said 'Me original', you are right!  Wow, pat yourself on the back.  If you got it wrong, don't worry, I did too.  Let's see what this thing actually produces after hoisting, and then we'll tackle it step by step.

After Hoisting

var f;  //gets value of undefined
function f() {
    console.log("Me duplicate.");
}
f = function() {
    console.log("Me original.");
}
f();

First thing that happens is that the declaration of f gets hoisted.  Then, the function f gets hoisted.  Even though this looks weird, so far we are ok.  Odd as it looks, this is valid JS.  We just have a single variable, f, that contains a function that prints out 'Me duplicate.'.  Next we redefine f to print out 'Me original.'.  Finally we have our invocation.  This calls f which has been reassigned to an anonymous function that prints out 'Me original.'

Hopefully all this helps clear up the mystery of hoisting in JavaScript.


A lot of these examples were taken from some stackoverflow questions:
http://stackoverflow.com/questions/23889317/explain-this-javascript-name-clash
http://stackoverflow.com/questions/336859/var-functionname-function-vs-function-functionname

Post a Comment