views:

751

answers:

4

Hello,

It seems I don't understand javascript callbacks quite as well as I thought.

In the following example, I would think that each copy of function in setTimeout would refer to its own copy of the variable "index". Therefore, running the example should produce the following alerts: "zero" "one" "two".

var array = ["zero", "one", "two"];
var out = "";
for(var i = 0; i < 3; i++){
    var index = i;
    setTimeout(  function(){alert(array[index])},  1 );
}

However, it seems that theres only one copy of the index variable, and all copies of the callback function point to the same variable, giving me the following alerts: "two" "two" "two".

The following analagous (I thought) example in java prints "zero" "one" "two", as expected.

import java.util.ArrayList;
import java.util.List;


public class CallBacks {

  public static void main(String[] args) {

    String[] array = {"zero", "one", "two"};
    List<Callback> callBacks = new ArrayList<Callback>();
    for(int i = 0; i<3; i++){
      final String print = array[i];
      callBacks.add(
              new Callback(){
                public void execute(){
                  System.out.println(print);
                }
              }
      );
    }
    for(Callback cb : callBacks){
      cb.execute();
    }
  }

  private interface Callback{
    public void execute();
  }

}

Can anyone explain to me why the js example doesn't work, and perhaps compare what's going on in the two examples?

+1  A: 

index changes with each iteration of the loop. What you want is to place index into a closure not affected by the loop:

var array = ["zero", "one", "two"];
var out = "";
for(var i = 0; i < 3; i++) {
    (function(index) {
        setTimeout(  function(){alert(array[index])},  1 );
    })(i)
}
Crescent Fresh
Thanks. I guess I thought that each iteration of the for loop would create a closure.
morgancodes
Well yes, technically I left that part out. There *is* a closure around index. But it is declared in an outer scope to the function passed to setTimeout. "closure" != "unchanging variable".
Crescent Fresh
Cool. One more question: why does the function declaration need to be wrapped in parens?
morgancodes
@morgancodes: that's just convention; also, there might be some cases where the parser might otherwise produce incorrect results
Christoph
Firefox tells me there's a syntax error if I leave them out.
morgancodes
closures in javascript (references to variables outside the function) retain their association... if you refer to a loop variable in a function called after the loop ends, in the function call you get the value of that variable after the loop.
Jason S
the function call/parens/etc create a copy of the value "i" into the variable "index", at the time of each iteration of the loop.
Jason S
+1  A: 

In javascript the for loop does not have it's own scope - so a var you create inside a loop is not different from one defined outside of it.

Simon Groenewolt
A: 

I asked an almost identical question yesterday and got a couple of different variations in the answers and some discussion on the merits of each. Might be worth a look

meouw
A: 

Private Members In JavaScript is my favorite reference on closures. It is written as a recipe for providing private variables in JS objects (maybe useful, maybe not) but in the process goes through an excellent introduction of how closures work in general, and especially in JS.

The comp.lang.javascript FAQ has a worthwhile section on closures as well.

Closures can be confusing so I put together a working example of the shortest useful closure I've come across, which was in the Rhino book.

Oh and I don't have enough rep to comment yet :) but I saw your question above about why you have to put parenthesis around functions sometimes. That's only necessary when you invoke an anonymous function inline. For example the parenthesis are required here:

(function (arg) {alert(arg);})('hi world');

Because you are creating a function and then immediately invoking it. But the parens are not necessary in the usual case where you are just declaring a named function for later use.

Noah Sussman
Thanks Noah. that rhino book example is cool.
morgancodes