1

Possible Duplicate:
Javascript closure inside loops - simple practical example

I was trying to code something similar to this :

var funcs = [];
for (var i=0; i<5 ; ++i) {
    funcs[i]=function() {
        alert(i);
    };
}

Obviously, calling funcs[0] won't alert 0 as expected, simply because the variable i is captured by the anonymous function, and calling any of funcs[0..4] will alert '4' (the value of i after the last iteration and shared by all created functions).

The first work around that comes to my mind is using some kind of function generator :

var funcs = [];
for (var i=0; i<5 ; ++i) {
    funcs[i]=(function(cap) {
        return function() {alert(cap)};
    })(i);
}

This do the trick, but seems really puzzling and hard to read. Is there any better way to get the intended behavior without using a function wrapper?

Community
  • 1
  • 1
sitifensys
  • 2,024
  • 16
  • 29
  • Couldn't you use `funcs[i].i = i;` after the function statement? – Waleed Khan Sep 07 '12 at 14:26
  • 1
    Can't recreate (Chrome 21 / Mac 10.7) http://jsfiddle.net/VDm5C/ – Waleed Khan Sep 07 '12 at 14:27
  • Nop, because in the real case that I'm working on, the scope is set on calling the function `funcs[i].call(someObject, ..)`. This will override `this`, and `this.i` won't be accessible. – sitifensys Sep 07 '12 at 14:29
  • JavaScript scope is at the `function` level. Thus when you create functions in another function, they share the parent function local variables. Opening a block (with `{ ... }`) does **not** create a new scope, as it does in C++ or Java. – Pointy Sep 07 '12 at 14:29
  • @arxanas: No. JavaScript doesn't work that way. – gen_Eric Sep 07 '12 at 14:29
  • @sitifensys: As Pointy says, JS var scope is at function level. So you need to do whatever you want (which happens to be: create a function) inside the body of a function (to distinguish scope). Possibly hard to read but necessary. – Jon Sep 07 '12 at 14:30
  • 1
    @arxanas: Your example is slightly flawed. It works because when you are calling each of the functions, you are in a `for` loop, and changing the value of `i` to be the correct value. Change your 2nd `for` to use another var, and it won't work. http://jsfiddle.net/VDm5C/1/ – gen_Eric Sep 07 '12 at 14:35

5 Answers5

2

The .bind function allows you to pre-bind additional parameters to a bound function:

var funcs = [];
for (var i=0; i<5 ; ++i) {
    funcs[i]=function(i) {
        alert(i);
    }.bind(this, i);
}

This is an ES5 function, so should work on IE9+, Chrome, Safari, Firefox:

Alnitak
  • 334,560
  • 70
  • 407
  • 495
2

You should write it as simple as you can. I think it's easy to understand, but hard to read. So one way to simplify it is to use Nested coding style. I don't think it can't be simpler than as it is now.

I'd suggest this way:

var funcs = [];

for (var i = 0; i < 5; ++i) {
    funcs[i] = (
        function (cap) {
            return function () { alert(cap) };
        }
    )(i);
}
Rikki
  • 3,338
  • 1
  • 22
  • 34
1

IMHO, named functions are often superior, for both performance and readability reasons. Why not do it like this:

function foo (cap) {
    return function () { alert(cap) };
}

var funcs = [];
for (var i=0; i<5 ; ++i) {
    funcs[i]=foo(i);
}
Linus Thiel
  • 38,647
  • 9
  • 109
  • 104
  • Because you need write a top-level function that is not used in any place else? – Jon Sep 07 '12 at 14:32
  • 2
    Jon: It doesn't have to be top-level, it just has to be in scope. – Linus Thiel Sep 07 '12 at 14:33
  • Sure, wrong expression on my part; pretend it's not there. It's still not ideal, it's really the same undesirable technique as e.g. creating classes to simulate closures in languages such as C++ and C#. Both of them have added lambdas specifically so that you don't have to write this kind of "machinery" code. – Jon Sep 07 '12 at 14:35
  • Ideal is subjective. Apparently, @sitifensys thinks the anonymous closure is not ideal. Why are we arguing this? – Linus Thiel Sep 07 '12 at 14:39
  • We are not. I just answered your rhetorical question by voicing an opinion. – Jon Sep 07 '12 at 14:41
  • @Jon: Thanks, duly noted! Regrettably, there are neither magic bullets not pixie dust. – Linus Thiel Sep 07 '12 at 14:54
0

When you don't want to embed your code with those anonymous functions, a solution is to define (named) class embedding both the state (i) and the function (in the prototype). That's more LOC but sometimes more readable :

var funcs = [];
function MyFunc(i) {
   this.i=i;
}
MyFunc.prototype.doIt = function(){
   alert(this.i);
};
for (var i=0; i<5 ; ++i) {
    funcs[i]=new MyFunc(i);
}

funcs[2].doIt();​
Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
0

Try this:

var funcs = [0, 1, 2, 3, 4].map(function(i) {
    return function() {alert(i);};
});

Note: map isn't suppoted by IE8 and older, but there's a common polyfill for it.

MaxArt
  • 22,200
  • 10
  • 82
  • 81
  • I nearly wrote an answer like this, but then decided that `[0, 1, 2, 3, 4, ...]` doesn't scale... – Alnitak Sep 07 '12 at 14:32
  • @Alnitak I know that feel, bro. I just wanted to show something different. This all belongs to some array generation algorithm. Maybe splitting strings, or creating `Uint8Array`, or whatever... – MaxArt Sep 07 '12 at 14:34
  • ah, now _that_ I _can_ solve: `Array.range = function f(m, n, z) { return m <=-- n ? (z = f(m, n), z.push(n), z) : [] };` – Alnitak Sep 07 '12 at 14:35