Variable Hoisting in JavaScript

The JavaScript Variable Scope article covered the basics of variable scope in JavaScript. Today I’d like to continue that discussion, exploring a more complicated concept called hoisting, where the names of variables declared inside of a function are hoisted to the front of the function, so that they may be accessed, even before they are defined.

How do it…

Here is a simple example of hoisting:

function foo() {
    alert(a);
    var a = 1;
}
foo();

You probably expect that the code above would cause an error, because the variable a is used before it is defined. It will actually alert undefined, because the JavaScript interpreter hoists all variable names used inside a function to the front of the function making them available right away (even though they have no value yet). This means that the code above was interpreted like:

function foo() {
    var a;
    alert(a);
    a = 1;
}
foo();

Now that you know what hoisting is, here is a trickier example1:

var foo = 1;
function bar() {
	if (!foo) {
		var foo = 10;
	}
	alert(foo);
}
bar();

This will alert 10, because the if statement does not create an execution context (as in other languages), and bar is interpreted as:

var foo = 1;
function bar() {
    var foo;
	if (!foo) {
		foo = 10;
	}
	alert(foo);
}
bar();

The same would happen, even if var foo = 10; was coded in a way that it would never execute:

var foo = 1;
function bar() {
	if (false) {
		var foo = 10;
	}
	alert(foo);
}
bar();

Here is another tricky example, using function names1:

var foo = 1;
function bar() {
	foo = 10;
	return;
	function foo() {}
}
bar();
alert(foo);

It will alert 1, because bar is interpreted as:

var foo = 1;
function bar() {
    var foo = function() {};
	foo = 10;
	return;
}
bar();
alert(foo);

Keep in mind that hoisting does not occur during scope chain lookups from inner functions:

function foo() {
    console.log(x); // will throw an error
    function bar() {
        x = 1;
    }
    bar();
}
foo();
console.log(x);

The first console.log will throw an error, because x is not hoisted to the execution context of foo. It will be added to window after foo() is called, but not until function bar executes. It will never be added to the execution context of foo().

How it works…

JavaScript has function-level scope, which we call the execution context. Variables declared inside of an execution context are hoisted to the front of the execution context, even if the variables are declared in code that will never execute. The interpreter hoists all variable names, including function names. Variables declared with var will be undefined, while the entire function definition is hoisted. Consequently, I strongly recommend that all variables (including those used by for loops) and functions, should be declared at the front of a function, using a single var statement, to avoid any unintentional hoisting.

function foo(a, b, c) {
    var i = 1,
    	j = 4,
    	baz = "something";

    for (; i < j; i++) {
        alert(baz);
    }
}

Lastly, we should discuss the priority by which variable names enter a scope. There are four basic ways for a variable name to enter an execution context1:

  1. Language-defined: All scopes are, by default, given the names this and arguments.
  2. Formal parameters: Functions can have named formal parameters, which are scoped to the body of that function.
  3. Function declarations: In the form of function foo() {}.
  4. Variable declarations: In the form of var foo;.

For the most part the latest variable name will replace the earlier, so if you define a formal function parameter as foo and then define a function with the same name, then the function would silently replace the formal parameter. The exception to this is variable declarations, which will replace language-defined names, but won’t replace arguments or function declarations.

Hoisting allows you to accidentally do some nasty things, especially if you muck around using reserved words (aka. arguments) for variable names. For this reason, I recommend not reusing variable names, but instead always declaring a new variable when you need to store a new value, and never using Reserved Words in JavaScript.

References


  1. http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html