Javascript: The Scope Pitfall

For the past few weeks I've been programming almost exclusively in Javascript. And to be quite honest it's not as bad as you may think. In fact, Javascript is quite a nice language, as long as you are aware of its quirks. One quirk that bit me in the ass a few times already is its lack of block scopes. To make the problem clear, let's look at a code fragment:

var elements = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
for(var i = 0; i < elements.length; i++) {
  var el = elements[i];
  console.log(el);
}

What does this code fragment print? Of course, it prints each of the elements in the array, so first 0, 1, 2, 3 etc. Easy enough. Now let's adapt that a little bit:

var elements = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
var fns = [];

for(var i = 0; i < elements.length; i++) {
  var el = elements[i];
  fns.push(function() {
      console.log(el);
    });
}
fns[2]();

This code fragment uses a powerful feature of Javascript: first order functions. Functions are values that can be put in a variable, including arrays as this code demonstrates. In this fragment, the for-loop builds up an array called fns with functions that print an element of the list. Later on we can then pick one of the functions from that array and invoke it. The last line demonstrates this, it executes the 3rd function in the array. So what will that print?

If you're a C/Java/C# programmer you'll probably be guessing that it would print 2. In every iteration a variable el is declared that contains the current element from the array. The function that is generated on the fly contains a reference to el and will therefore be part of its closure. Logically, this means that in the first function captures the value 0 for el, the second function value 1 etc. Sadly, this is not the case. Javascript only has function scopes, not block scopes (for constructs like the for-loop). In effect, what you're actually executing here is the following:

var elements = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
var fns = [];
var el;
for(var i = 0; i < elements.length; i++) {
  el = elements[i];
  fns.push(function() {
      console.log(el);
    });
}
fns[2]();

Alright, fine. Why is that a problem? You have to realize that the closure of the generated functions contain pointers to variables, not a snapshot of the values at the point of definition, meaning that if variables change, these changes are visible from within the function. This is a very powerful feature, but also a potentially very confusing one. Since there is only one variable el, to which a new value is assigned every iteration, and each function points to this same variable, every function in the array will produce the same value: 9 (the last value assigned to el in the for-loop).

If you have problems getting your head around that, don't feel bad, it took me a while too.

So, how can we fix this? Well, we can artificially introduce scopes by creating new functions and immediately invoking them: 

var elements = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
var fns = [];

for(var i = 0; i < elements.length; i++) {
  (function() {
    var el = elements[i];
    fns.push(function() {
        console.log(el);
      });
  }());
}

fns[2]();

So what's new here is the (function() { bit and the }()); at the end. What this simple trick does is define an anonymous function and immediately invoke it. What's different than before is that a new scope is used for each iteration of the for loop, containing a fresh variable el. The functions that are generated now each refer to a different el. Thus, the fn[2]() call will now produce 2, as you would expect.