Hoisting in JavaScript

In this post I will describe what is variable hoisting in JavaScript. But before we go into detailed explanations let’s look at a few code examples.

Let’s start with function bad() that tries to access not defined variable:

function bad() {
    console.log(notDefinedVariable);
}

bad();
// result:
// "ReferenceError: notDefinedVariable is not defined

As we would expected attempt to read notDefinedVariable ends with an error, namely ReferenceError exception.

Now you may be wondering what will happen when we try to write value to not defined variable:

function bad2() {
    notDefinedVariable = 'fufufu';
}

bad2(); // no error

console.log(notDefinedVariable);
// prints: "fufufu"

In this case we get no error and everything seems to work, but after a call to bad2() notDefinedVariable is still visible. What happened here is that we accidentally create notDefinedVariable global variable.

Using global variables is regarded by many programmers as bad practice, especially when you can create them accidentally. To prevent errors and encourage good programming style ECMAScript 5 standard (which defines JavaScript language) introduced so called strict mode. We can enable strict mode by beginning function with 'use strict'; instruction. With strict mode enabled we get ReferenceError exception when we try to write to undefined variable:

function bad3() {
    'use strict';
    notDefinedVariable = 'fufufu';
}

bad3();
// result: "ReferenceError: undefinedVariable is not defined

You may be wondering what will happen when we declare variable but didn’t assign any value to it, will we get an error while accessing variable value or not?

function soSo() {
    'use strict';
    
    var foo;
    console.log(foo);
}

soSo();
// prints: undefined

We didn’t get an error, that’s because variables declared using var keyword initially have value of undefined until explicitly assigned by the user.

Now let’s look on a bit more complicated example:

function hoisting1() {
    'use strict';

    console.log(x);
    
    var x = 3;
    console.log(x);
}

hoisting1();
// prints: undefined 3

You may wonder what happened here. We already know that reading from undefined variables causes ReferenceError exception, but why first call to console.log(x) didn’t throw any? And what about undefined value that was printed, we know that this is the value of declared but not initialized variables. So to sum up this function behaves as if variable x was declared at the very beginning of hoisting1 function:

function hoisting1() {
    'use strict';
    
    var x;

    console.log(x);
    x = 3;
    console.log(x);
}

And this is what hoisting is all about. In JavaScript it doesn’t matter where you declare variables inside function body because they declarations will be implicitly moved to the beginning of the function. For example func1() when processed by JavaScript interpreter behaves exactly the same as func2():

function func1() {
    'use strict';
    
    var x = 1;
    console.log(x);

    for (var i = 0; i < 3; i += 1) {
        console.log(i);
    }

    if (x > 0.5) {
       var y = 2*x;
       console.log(y);
    }
}

function func2() {
    'use strict';

    var x, i, y;

    x = 1;
    console.log(x);

    for (i = 0; i < 3; i += 1) {
        console.log(i);
    }

    if (x > 0.5) {
       y = 2*x;
       console.log(y);
    }
}

As you may expect hoisting can make troubles when we are not alert, for example can you spot a bug here:

function withBug() {
    'use strict';
    var arrayI = [1,2,3],
        arrayJ = [3,2,1];

    for (var i = 0; i < arrayI.length; i += 1) {
        arrayI[i] += 1;
    }

    for (var j = 0; j < arrayJ.length; j += 1) {
        if (arrayJ[j] == 2) {
            console.log(arrayJ[i]);
            break;
        }
    }
}

withBug();
// prints: undefined

If i was accessible only inside for(var i ...) loop interpreter would spot problem with console.log(arrayJ[i]) line and would thrown a ReferenceError exception. Unfortunately because of hoisting variable i is accessible through entire function body and only wrong behaviour of our program can tell us that something is wrong.

So how can we make our programs secure against bugs caused by hoisting? The simples thing to do is to declare all function variables at the beginning of function body, thus making hoisting explicit:

function sum(array) {
    'use strict';

    // declare all variables at the beginning   
    var arrSum = 0, i;
  
    for (i = 0; i < array.length; i += 1) {
      arrSum += array[i];
    }
  
    return arrSum;
}

This is simple and effective solution but a bit cumbersome to use, it would be better to declare variables in the place of their first usage right? So the second solution is to just ignore the problem, if you are keeping size of your function small (no longer than 20 lines of code) and you follow good coding practices (especially you give your variables good descriptive names, and prefer forEach method to for loops) you should have no problems with hoisting.

In some cases you really want to reduce visibility of a variable to some block of code, the popular solution to this problem is to use IIFE pattern:

function iife() {
    'use strict';
    
    // following line produces: ReferenceError: i is not defined
    // console.log('before IIFE: ' + i);
  
    // iife expression - we declare anonymous
    // function and immediately invoke it
    (function() {
      for (var i = 0; i < 3; i+=1) {
        console.log(i);
      }
    }());
  
    // following line produces: ReferenceError: i is not defined
    // console.log('after IIFE: ' + i);
}

iife();
// prints: 0 1 2

Here variable i is visible only inside IIFE expression. Using IIFE has it’s own problems especially when we want to access this value inside IIFE expression. We must either pass this via local variable:

function foo() {
	// it is a convention to call such a variable
	// that or self
	var that = this;

	(function() {
		that.someMethod();
	}());
}

or use call() to invoke function:

function foo() {
	(function() {
		this.someMethod();
	}).call(this);
}

Almost every popular language (e.g. Java, C#, C++) follows block scoping rules, this means that variable is visible only inside a block of code in which it is declared. For example in Java:

int i = 0;
{
	int j = 3;
	// we can use i and j here
}
// j no longer visible here
for (int k = 0; k < 3; k++ ) {
	// k and i are visible here
}
// k is no longer visible here
// but we can access i

Compare this with JavaScript version:

// can use i, j and k here
var i = 0;
// can use i, j and k here
{
	var j = 3;
	// can use i, j and k here
}
// can use i, j and k here
for (var k = 0; k < 3; k++ ) {
	// can use i, j and k here
}
// can use i, j and k here

If you have this strange feeling that something is wrong here you are not the only one. JavaScript community decided to introduce proper lexical scoping to JavaScript in ECMAScript 2015 standard (sometimes called ES6). ECMAScript 2015 introduces a new JavaScript keyword let that works like var but with lexical scoping. Here’s how our example looks with let:

function letTest() {
	'use strict';
	let i = 0;
	{
		let j = 3;
	}
	// cannot use j here
	for (let k = 0; k < 10; k++) {
		console.log(k);
	}
	// cannot use k here 
}

letTest();

let also prohibits redeclarations of variables:

function foo() {
	var i = 3;
	var i = 7; // ok

	let j = 3;
	let j = 7; // error
}

Support of ES6 among browsers is pretty good right now (you may also use it with node.js), but I guess if you have a chance to work with ES6 you most probably will be transpiling ES6 code into ES5 code (a plain old JavaScript) using Babel or some other transpiler.

I hope you now know what hoisting is and what troubles it can make. If you have any remarks how I can improve this article please leave a comment.

marcin-chwedczuk

A Programmer, A Geek, A Human