views:

749

answers:

4

Here is a simplified version of something I'm trying to run:

for (var i = 0; i < results.length; i++) {
 marker = results[i];
 google.maps.event.addListener(marker, 'click', function() { 
   change_selection(i);
       }); 
}

but I'm finding that every listener uses the value of results.length (the value when the for loop terminates). How can I add listeners such that each uses the value of i at the time I add it, rather than the reference to i?

+17  A: 

You need to create a separate scope that saves the variable in it's current state by passing it as a function parameter:

for (var i = 0; i < results.length; i++) {
  (function (i) {
    marker = results[i];
    google.maps.event.addListener(marker, 'click', function() { 
      change_selection(i);
    }); 
  })(i);
}

By creating an anonymous function and calling it with the variable as the first argument, you're passing-by-value to the function and creating a closure.

Andy E
ah, you beat me to it!
David Murdoch
@David Murdoch: happens to all of us! +1 for your answer.
Andy E
Thanks Andy and David, your answers worked! Appreciate fast and accurate responses.
ryan
+1  A: 

You're winding up with a closure. Here's an article on closures and how to work with them. Check out Example 5 on the page; that's the scenario you're dealing with.

ajm
+4  A: 

closures:

for (var i = 0, l= results.length; i < l; i++) {
    marker = results[i];
    (function(index){
        google.maps.event.addListener(marker, 'click', function() { 
            change_selection(index);
        }); 
    })(i);
}
David Murdoch
+7  A: 

As well as the closures, you can use function.bind:

google.maps.event.addListener(marker, 'click', change_selection.bind(null, i));

passes the value of i in as an argument to the function when called. (null is for binding this, which you don't need in this case.)

function.bind was introduced by the Prototype framework and has been standardised in ECMAScript Fifth Edition. Until browsers all support it natively, you can add your own function.bind support using closures:

if (!('bind' in Function.prototype)) {
    Function.prototype.bind= function(owner) {
        var that= this;
        var args= Array.prototype.slice.call(arguments, 1);
        return function() {
            return that.apply(owner,
                args.length===0? arguments : arguments.length===0? args :
                args.concat(Array.prototype.slice.call(arguments, 0))
            );
        };
    };
}
bobince
Just noticed this, +1. I'm quite a fan of `bind` and can't wait for the native implementations to roll out.
Andy E
Thanks for sharing this.
Allain Lalonde