1
function init(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 foo() {
    var list = init([1,2,3]);
    for (var j = 0; j<list.length; j++) {
        list[j]();
    }
}
foo();

The script alerts:

  • item3 undefined
  • item3 undefined
  • item3 undefined
rolz
  • 80
  • 1
  • 6

3 Answers3

1

By the time you call list[j]();, the value of i is 3 (as the for (var i = 0; i<list.length; i++) { will have reached the end.

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
1

It's the good-old i + scope issue again ;)... the solution:

function init(list)
{
    var item, result = [];
    for (var i = 0; i<list.length; i++)
    {
        item = 'item' + list[i];
        result.push((function(val, idx)
        {//create this function with its own scope
            return function()
            {//return this function, which has access to val and idx
                alert(val+' '+list[idx]);
            };
        }(i, item));//pass vals of i and item to function, and call it here (IIFE)
    }
    return result;
}

Why? all functions created in a given scope, have access to any variable declared in that higher scope. So the functions you're pushing into result have, indeed access to i, item and list. They have access, but don't own their own copy of those variables.
on each iteration of the for loop, i is incremented, so it's 0, 1, 2,3. When its value hits 3, the it's value is no longer < list.length so the loop ends. But inside the functions you're creating list[i] evaluates to list[3], which is undefined.

The same applies to item, the variable is declared in the init function, where it's reassigned on each iteration, too, hence whichever of the created functions you call, they will reference whatever was last assigned to item: "item3" in your case.

I've posted a fairly lengthy answer that explains this in detail, and I've added a lot of info to the tag-wiki, too. For now, you can easily test this like so:

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

This will alert something like:

//i = 0
item1 1
//i = 1
item2 1
item2 2
//i=2
item3 1
item3 2
item3 3
//outside loops, alert i:
3
Community
  • 1
  • 1
Elias Van Ootegem
  • 74,482
  • 9
  • 111
  • 149
  • Guys the code above is a question, I got the wrong answer that is why I am asking why it happen that way? My answer is item1 1, item2 2, item3 3. – rolz Aug 02 '13 at 09:52
  • @rolz: My answer starts off with code that works as expected, but after that, I explain _why_ your code alerts what it alerts, and I link to another answer with drawings and tons of extra info on how JS evaluates expressions – Elias Van Ootegem Aug 02 '13 at 09:58
  • @rolz: The construction that is used to solve this is often referred to as an IIFE. I've recently added a lor of details on the matter in the [tag wiki](http://stackoverflow.com/tags/iife/info), so if there's anything still unclear, you might want to check that out – Elias Van Ootegem Aug 02 '13 at 10:04
  • why is it no alert happens in this function call init([1,2,3])? – rolz Aug 02 '13 at 11:15
  • @rolz: If you're using my first snippet: when I call `result.push`, I'm passing a function object, returned by an IIFE. It's this returned function that has `alert(...)` in it's body. This function isn't called yet, so the alert isn't called. If you use my second snippet (which doesn't solve the scope-issue), there will be alerts (the ones I listed @ bottom of my answer) – Elias Van Ootegem Aug 02 '13 at 11:18
0

Because the internal function that you are storing in the array takes the reference to the variable not the value in each iteration. You can call a function with i as a parameters that return the function that you want to keep in the array, and because you are creating a new function and javascript functions create a new scope and take the value of the parameter, it take the position of memory that i points in that moment that you call the function;

function init(list) {
    var result = [];

    for (var i = 0; i<list.length; i++) {
        var item = 'item' + list[i];
              result.push( (function (i){ return function() { alert( item + ' ' +list[i] )};})(i))
    }
    return result;
}
function foo() {
    var list = init([1,2,3]);
    for (var j = 0; j<list.length; j++) {
        list[j]();
    }
}
foo();
Ivan Fraixedes
  • 550
  • 3
  • 12
  • By the way what this list[j]() does? – rolz Aug 02 '13 at 10:01
  • You're not solving any of the problems: you suggest masking `i` using an argument, but then fail to pass it (`list[j]()` should be `list[j](j)`), but the `item` var is still not scoped correctly, and will be `item3` every time – Elias Van Ootegem Aug 02 '13 at 10:08