5

I am reading an article (JavaScript Closures for Dummies) and one of the examples is as follows.

function buildList(list) {
  var result = [];
  for (var i = 0; i < list.length; i++) {
    var item = 'item' + list[i];
    result.push( function() {alert(item + ' ' + list[i])} );
  }
  return result;
}

function testList() {
  var fnlist = buildList([1,2,3]);
  // using j only to help prevent confusion - could use i
  for (var j = 0; j < fnlist.length; j++) {
    fnlist[j]();
  }
}

testList();

When testList is called, an alert box that says "item3 undefined". The article has this explanation:

When the anonymous functions are called on the line fnlist[j](); they all use the same single closure, and they use the current value for i and item within that one closure (where i has a value of 3 because the loop had completed, and item has a value of 'item3').

Why does item have a value of 'item3'? Doesn't the for loop end when i becomes 3? If it ends shouldn't item still be 'item2'? Or is the variable item created again when testList calls the functions?

Anurag
  • 140,337
  • 36
  • 221
  • 257
hekevintran
  • 22,822
  • 32
  • 111
  • 180

5 Answers5

4

The for loop within buildList completes before you do the following:

for (var j = 0; j < fnlist.length; j++) {
  fnlist[j]();
}

... therefore, by that time (when you call each function), the variable item will be whatever was last assigned to it (i.e. "item3"), and i will be 3 (as a result of the last i++ operation), and list[3] is undefined.

It's all to do with the fact that the loop completes before you call the closure'd function. To prevent this, you could create a new closure, like so:

function buildList(list) {
  var result = [];
  for (var i = 0; i < list.length; i++) {
    var item = 'item' + list[i];
    result.push(
        (function(item, i){
            // Now we have our own "local" copies of `item` and `i`
            return function() {
                console.log(item + ' ' + list[i])
            };
        })(item, i)
    );
  }
  return result;
}
James
  • 109,676
  • 31
  • 162
  • 175
  • @J-P, Thanks for the great response. I now understand why it was undefined but can I ask why you're example works. Why is there now a new closure created for each of the functions now stored in fnlist? – screenm0nkey May 19 '10 at 13:45
  • That function that's declared and called inside the call to "result.push" creates its own closure. – Pointy May 19 '10 at 13:49
  • @Pointy, Why does it create it's own closure? Because it's an anonymous function? – screenm0nkey May 19 '10 at 13:54
  • @Nick, It creates its own closure simply because it's a function. A function within a function forms a closure... So, on each iteration of the loop we're creating a new closure that will retain the current values of `i` and `item`. – James May 19 '10 at 13:58
4

You're close...

Why does item have a value of 'item3'? Doesn't the for loop end when i becomes 3?

Yes.

If it ends shouldn't item still be 'item2'?

Nope. This example is a little tricky. During the last iteration of the loop, i is 2, but it references the 3rd element of the list array, which is 3. In other words, item == 'item' + list[2] == 'item3'

Or is the variable item created again when testList calls the functions?

No, you were almost right the first time. I think you just missed that item[2] has the value of 3.

Kenan Banks
  • 207,056
  • 34
  • 155
  • 173
  • Wow that's so simple. How did I miss that? The whole start-counting-at-zero thing always messes me up. Thanks! – hekevintran Jun 01 '09 at 03:11
  • If that was the only obstacle to you understanding javascript closure, give yourself a pat on the back! I think that example was unnecessarily complicated. – Kenan Banks Jun 01 '09 at 04:50
2

The list variable is stored in closure as you say.

Actually you can access the list variable, but you are trying to access list[3]. After all, the i variable is also stored as a closure and it's value is 3 when the console.log function is called.

Tomas
  • 5,067
  • 1
  • 35
  • 39
2

I think the point you are missing is that list[i] is underfined because i is 3, and list is only defined for 0..2.

Fred
  • 8,582
  • 1
  • 21
  • 27
0

The loop ends when i becomes 3, but the "item" variable stored in the closure, and displayed by alert, is set to

var item = 'item' + list[i];

the text 'item' + the value at list[2]. The third list item is 3, so the text is item3

Todd Gardner
  • 13,313
  • 39
  • 51