5

I would like to do the something along the following:

for (var i = 0; i < 10; ++i) {
    createButton(x, y, function() { alert("button " + i + " pressed"); }
}

The problem with this is that I always get the final value of i because Javascript's closure is not by-value.
So how can I do this with javascript?

HugoTeixeira
  • 4,674
  • 3
  • 22
  • 32
shoosh
  • 76,898
  • 55
  • 205
  • 325
  • You could edit createButton, allowing it to have another argument passed, that is, i. This way you can store i in your createButton function and use it. –  Apr 05 '11 at 16:55
  • possible duplicate of [Javascript closure inside loops - simple practical example](http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example) – rds Jan 17 '13 at 17:53

5 Answers5

7

One solution, if you're coding for a browser that uses JavaScript 1.7 or higher, is to use the let keyword:

for(var i = 0; i < 10; ++i) {
    let index = i;
    createButton(x, y, function() { alert("button " + index + " pressed"); }
}

From the MDC Doc Center:

The let keyword causes the item variable to be created with block level scope, causing a new reference to be created for each iteration of the for loop. This means that a separate variable is captured for each closure, solving the problem caused by the shared environment.

Check out the MDC Doc Center for the traditional approach (creating another closure).

McStretch
  • 20,495
  • 2
  • 35
  • 40
6
for(var i = 0; i < 10; i++) {
    (function(i) {
        createButton(function() { alert("button " + i + " pressed"); });
    })(i);
}

Note that JSLint doesn't like this pattern. It throws "Don't make functions within a loop.".

Live demo: http://jsfiddle.net/simevidas/ZKeXX/

Šime Vidas
  • 182,163
  • 62
  • 281
  • 385
  • I like this answer more than Peter's due to its cleanliness. It would be nice if more browsers supported the `let` keyword though. – McStretch Apr 05 '11 at 17:13
  • @McStretch I tried to make a `let` demo in jsFiddle, but I couldn't make it work. See here: http://jsfiddle.net/simevidas/ZKeXX/1/ Firefox 4 throws an error. – Šime Vidas Apr 05 '11 at 17:31
  • @Šime - I just saw this question: http://stackoverflow.com/questions/2356830/what-browsers-currently-support-javascripts-let-keyword , which says you have to explicitly tell the browser (Firefox only right now) that you're using 1.7. Here's an updated fiddle: http://jsfiddle.net/simevidas/ZKeXX/1/. Pretty lame huh? Obviously not really a good solution until everyone supports 1.7 or higher, and who know when that will happen. – McStretch Apr 05 '11 at 17:52
  • @McStretch I see. The updated demo (which works in Firefox) is here: http://jsfiddle.net/simevidas/ZKeXX/2/ It seems that we won't be able to use `let` for a long time. Even if IE10 implements it, we'd have to wait until IE9 exits the market (and that may not happen before 2020). – Šime Vidas Apr 05 '11 at 18:06
4

Create a new scope for the closure by executing another function:

for(var i = 0; i < 10; ++i) {
    createButton(x,y, function(value) { return function() { alert(...); }; }(i));
}

http://www.mennovanslooten.nl/blog/post/62

BenMorel
  • 34,448
  • 50
  • 182
  • 322
Peter Davis
  • 761
  • 7
  • 13
  • 1. You might want to place a semi-colon at the end of the return statement to make the code a bit more readable. 2. IIFE's are usually wrapped in parens. – Šime Vidas Apr 05 '11 at 17:02
1

You need to put the closure into a separate function.

for(var dontUse = 0; dontUse < 10; ++dontUse) {
    (function(i) {
        createButton(x, y, function() { alert("button " + i + " pressed"); }
    })(dontUse);
}

Thise code creates an anonymous function that takes i as a parameter for each iteration of the loop.
Since this anonymous function has a separate i parameter for each iteration, it fixes the problem.

This is equivalent to

function createIndexedButton(i) {
    createButton(x, y, function() { alert("button " + i + " pressed"); }
}

for(var i = 0; i < 10; ++i) {
    createIndexedButton(i);
}
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
0
for(var i = 0; i < 10; ++i) {
    createButton(x, y, (function(n) {
        return function() {
            alert("button " + n + " pressed");
        }
    }(i));
}

The anonymous function on the outside is automatically invoked and creates a new closure with n in its scope, where that takes the then current value of i each time it's invoked.

Alnitak
  • 334,560
  • 70
  • 407
  • 495