views:

53

answers:

3

Hey,

my question is actually one of understanding - I have a working solution, I just don't understand how it works.

Okay, so - what I'm trying to do is adding a setTimeout in a loop, and passing a changing value through it. Example:

for (i=0;i<11;i++)
{
     setTimeout("alert(i)",1000);
}

If I understood correctly, this doesnt work because Javascript does not (like PHP) pass the value of i to the function, but passes a reference of i - which in turn is not static, but continues to change with the counter.

I found a solution, which goes like this:

for (i=0;i<11;i++)
{
    setTimeout(function(x){return function(){alert(x)};}(i),1000);
}

I don't really understand what this actually does. It looks like it passes a the "alert" function back to the calling function, but I can't make any sense of that.

I can work with this solution and also adapt it to other contexts, but I'd really like to understand all of my code, not just use stuff I found somewhere and be happy it works. And in addition, I'm looking for a slimmer version to achieve the same goal.

Thanks, Marco

+4  A: 

What this does:

function(x){return function(){alert(x)};}(i)

Is it takes a function:

function(x){ ...code... }

And executes it immediately, passing i (from the for loop) in as the only parameter (that's what the (i) on the end is for). This returns another function:

function(){ alert(x); }

It's that result that's being passed to setTimeout() as the function it calls when the timer's up, and it's not referencing the variable i in your loop that's changing, it's using the copy that was passed in when it created the new function.

Nick Craver
Thanks nick for taking the time to explain this to me - and thanks as well for the "+1". It's actually true, all of my page (airports.palzkill.de) - with the exception of the Google stuff - is selfmade.
Marco P.
+2  A: 

The reason you're calling a function that returns a function is that you need to have some way for the function being passed to setTimeout() to have a reference to the current value of i.

Because the code waits to run for 1000ms, the for loop will be complete before it runs, and the value if i will be 11.

But because a function has its own variable scope, you can pass the value of i into the function that is being called immediately, so that it is referenced by the local variable x, which the function being returned can reference when setTimeout() finally calls it.

for (i=0; i<11; i++) {
    setTimeout(function(x){
                 // CONTINUE HERE:
                 // x is a local variable to the function being executed
                 //    which references the current value of i

                 // A function is being returned to the setTimeout that
                 //    references the local x variable
                 return function(){ alert(x); };

               }(i) // START HERE:
                    // The "outer" function is executed immediately, passing the
                    //   current value of "i" as the argument.
     ,1000);
}

So you're ending up with an equivalent that would be something like this:

setTimeout( function(){ alert(x); }, 1000); //...where x === 0
setTimeout( function(){ alert(x); }, 1000); //...where x === 1
setTimeout( function(){ alert(x); }, 1000); //...where x === 2
setTimeout( function(){ alert(x); }, 1000); //...where x === 3
// etc.
patrick dw
Patrick, thank you so much. This was probably the missing link.
Marco P.
@Marco P. - You're welcome. :o)
patrick dw
A: 

Patrick and Nick helped me a big deal in understanding the whole thing, so I'd like to summarize it for everybody having the same problem as me:

setTimeout (as well as some other time-delayed functions like eventlisteners) seems to store the callback as a string, and then uses some kind of internal eval on this string, thus interpretes it as code.

That causes problems with loops and time-delayed functions, as their reference to the variable refers to the end result of that loop, or maybe a variable that is not even global.

As I understand it, the solution with the function-in-a-function solves this problem by giving the string back as a function result, which then contains the value, not the reference to a variable (alert("1") not alert(i)).

In regards to making the code shorter, my simple mind came to a simple solution. As the callback is expected to be a string, why not write the variables value into this string, and then give that back:

for (i=0;i<11;i++)
{
     setTimeout("alert("+i+")",1000);
}

Objectively this is probably not the best solution, but as it requires the smallest amount of code and the smallest amount of brain resources to understand how and why it works opposed to other solutions, I can work with it for now.

Thanks again to Patrick, Nick and the unknown guy who withdrew his answer for taking the time to help me with this!

Marco P.
Marco - You've got it partially right. A `setTimeout` can accept a String for its argument, which it will then try to `eval`. If a method (like alert) exists in the global namespace, it will fire. But `setTimeout` can also accept a function object as the argument. This is done by passing a anonymous function (which the code in your example was returning), or by passing a variable reference to a function. If a function is received, it will be called in the global namespace regardless of the fact that it may have originated in a private scope. In js, you can pass functions around like this.
patrick dw