views:

202

answers:

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?

+4  A: 

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.

Triptych
Wow that's so simple. How did I miss that? The whole start-counting-at-zero thing always messes me up. Thanks!
hekevintran
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.
Triptych
A: 

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
+2  A: 

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
+4  A: 

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;
}
J-P
@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?
Nick Lowman
That function that's declared and called inside the call to "result.push" creates its own closure.
Pointy
@Pointy, Why does it create it's own closure? Because it's an anonymous function?
Nick Lowman
@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`.
J-P
@J-P, Thanks man. I get it now. Thanks for your help.
Nick Lowman
+2  A: 

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