What Is Hoisting, Really?

- - | Comments

I use the word “hoisting” in relation to JavaScript pretty often. I’ve even written a blog post about how it’s one of the difficult parts of JavaScript to learn. In reality, hoisting isn’t part of JavaScript at all. There is no feature in the specification called hoisting, so I’ve decided to figure out what it really is.

You can find a lot of good information about hoisting over at Nettuts and Ben Cherry’s blog. In case you haven’t come across it before, hoisting is how JavaScript developers describe the existence of certain references before they seem to be declared. For example:

1
2
3
4
5
6
7
var foo = "hello";
console.log(foo); // "hello"
function example(){
    console.log(foo); // undefined
    var foo = "world";
}
example();

In this example, it seems like “hello” should be logged to the console, because the logging statement comes before the variable statement. The variable declaration is hoisted to the top of the function, as if the first line of the example function was var foo;

What is happening?

What we call hoisting manifests itself as seemingly rewritten source files that move the declaration of variables and functions to the top of a function. I have described it as the JavaScript interpreter rewriting your source code before actually intepreting it. JavaScript behaves as if it changes this code:

1
2
3
4
5
6
7
(function(){
    var hello = "world";
    //some other code
    var expression = function(){ alert("expression!"); };
    var foo = "bar";
    function declaration(){ alert("declaration!"); };
})();

Into this:

1
2
3
4
5
6
7
8
(function(){
    function declaration(){ alert("declaration!"); };
    var foo, hello, expression;
    hello = "world";
    //some other code
    expression = function(){ alert("expression!"); };
    foo = "bar";
})();

While this is a simple mental model to understand what is going on, this isn’t what happens at all. This feature of JavaScript semantics comes from a section in the ECMAScript specification called Entering Function Code and Declaration Binding Instantiation. Going through the specification is pretty tedious, but with careful reading, going through the background information, and my relatively more human readable translations of the spec, you should be able to understand what’s going on.

Background information

1
2
var test; // this is a variable declaration
function hello(){} // this is a function declaration

When a function is executed, an Execution Context is created. An Execution Context has a few different parts, but most importantly for this discussion, it contains a Lexical Environment. Conceptually, a Lexical Environment is an object that stores the bindings for identifiers that are used in the function. The Lexical environment is used to resolve identifiers when the function is actually executed.

Into the spec we go

Entering Function Code

As specified in section 10.4.3

  1. The following steps are performed when control enters the execution context for function code …
    1. Perform Declaration Binding Instantiation using the function code as described in 10.5.

Translation

Every time a function is called, before execution, go through the process of Declaration Binding Instantiation, as will be described next.

Function Declarations

As specified in section 10.5

  1. For each FunctionDeclaration f in code, in source text order do:
    1. Let fn be the Identifier in FunctionDeclaration f …
    2. Let fo be the result of instantiating FunctionDeclaration f as described in Clause 13 …
    3. Call env’s SetMutableBinding concrete method passing fn, fo, and strict as the arguments.

Translation

Go through each of the function declarations within this function, instantiate those functions, and store the binding in the current Lexical Environment.

1
2
3
4
// For example:
(function(){
    function foo(){}
})();

In this case, before the function is executed, the function declaration is added to the Lexical Environment with the identifier foo and the value of an instantiated function object.

Variable Declarations

As specified in section 10.5

  1. For each VariableDeclaration and VariableDeclarationNoIn d in code, in source text order do:
    1. Let dn be the Identifier in d.
    2. Let varAlreadyDeclared be the result of calling env’s HasBinding concrete method passing dn as the argument …
    3. If varAlreadyDeclared is false, then:
      1. Call env’s CreateMutableBinding concrete method passing dn and configurableBindings as the arguments.
      2. Call env’s SetMutableBinding concrete method passing dn, undefined, and strict as the arguments.

Translation

Go through each of the variable declarations within this function and explicitly store the value undefined in the Lexical Environment.

1
2
3
4
// For example:
(function(){
    var foo;
})();

In this case, before the function is executed, the identifier foo is added to the Lexical Environment with the value undefined

Out of the spec

Now that I’ve read through that portion a few times, I can tell what is actually happenning. It takes about a dozen reads to start glossing over the boilerplate that’s needed for a rigorous specifiation.

To summarize, when a function is first entered, before any of the lines are actually executed, the execution environment goes through the function’s source and picks out some special cases to deal with. First, it goes through each FunctionDeclaration, and adds references for each of them to the environment record. Then, it goes through each VariableDeclaration and adds references to the value undefined for each of them. Only after this process has completed, does the function body itself start to execute.

Now we know

Although I have no intention to stop using the word hoisting, I am happy to have a pretty good understanding of why it’s not completely precise. The simple mental model of source rewriting allows developers to quickly visualize hoisting’s consequences on scope, but it misses the nuance that exists within the JavaScript interpreter. I had a fun time getting more acquainted with the process of reading a specification, and I hope that you try your mind at it too!

See you in the comments.

Comments