Tuesday, May 28, 2013

Trick question : Closures in Javascript

Closures are one of the most powerful features of the Javascript language. If there is one thing that makes JS so versatile, it has to be closures. But they can be tricky if you do not understand:
  1. Closures capture the variable not just its value.
  2. Scope only changes using functions in Javascript.
So the question, What does the following code print:

var funcs = [];
// Setup
for (var i = 0 ; i < 10 ; i++) {
    funcs.push(function () { console.log(i) });
}
// Call
for (var j = 0 ; j < funcs.length ; j++) {
    funcs[j]();
}

The intuitive answer is 0,1,2,3…9 . However that is not the case. This is because each function in setup captured the variable i and not its value. When the for loop finished the variable i has a value 10 and therefore all functions print out 10. You now understand what (1) means. 

Now let’s make these functions print 0,1,2,3…9. The solution would be to create a new variable within the scope of the for loop so that each function gets a new variable. Now the only way to create a new scope is using a function (2). Lets say you don't know this and try the following which will not work. It will print 9 instead of 10 however since that is the last value that gets assigned to foo:

// Clear
funcs = [];
// Setup
for (var i = 0 ; i < 10 ; i++) {
    var foo = i;
    funcs.push(function () {
        console.log(foo)
    });
}
// Call
for (var j = 0 ; j < funcs.length ; j++) {
    funcs[j]();
}

To fix this we use the concept of immediately executing functions i.e. we declare a function (to create a new scope) and immediately execute it (to fall into that scope). The following will work. :

// Clear
funcs = [];
// Setup
for (var i = 0 ; i < 10 ; i++) {
    (function () {
        var foo = i;
        funcs.push(function () {
            console.log(foo)
        });
    })();
}
// Call
for (var j = 0 ; j < funcs.length ; j++) {
    funcs[j]();
}

You now understand (2). You can find the code here : http://jsfiddle.net/basarat/U9brW/

As a aside you might think the following is a good idea (will not work as expected):

// Setup
for (var i = 0 ; i < 10 ; i++) {       
    funcs.push(function () {
        var foo = i;
        console.log(foo)
    });   
}

But it’s actually no different than (will not work as expected):

// Setup
for (var i = 0 ; i < 10 ; i++) {
    funcs.push(function () { console.log(i) });
}

Because the only variable that is captured by the function is still i from the outer scope.

No comments:

Post a Comment