7

Consider the following Javascript code:

var a = [];

var f = function() {

    for (var i = 0; i < 3; i++) {
        a.push(function(){alert(i)});
    }
    for (var j = 0; j < 3; j++) {
        a[j]();
    }
};

The alerts print out '3' all three times. I want a different behaviour - in each iteration of the loop generate a function that prints the current value of i. I.e. 3 functions that print different indices.

Any ideas?

Anurag
  • 140,337
  • 36
  • 221
  • 257
Yaniv
  • 315
  • 1
  • 4
  • 8
  • Just to add this is because of Javascript has no concept of block scope only function scope, this thrown me too... http://www.mattfreeman.co.uk/2010/03/closures-scope-in-javascript-vs-c/ –  May 21 '10 at 08:07

5 Answers5

7

Create an anonymous function which accepts i as a parameter and returns that certain function:

for (var i = 0; i < 3; i++) {
    a.push((function(i) {
        return function() {
            alert(i);
        }
    })(i));
}

for (var j = 0; j < 3; j++) {
    a[j]();
}

Or do something similar: create an anonymous function which accepts i as a parameter to add the function to the array:

for (var i = 0; i < 3; i++) {
    (function(i) {
        a.push(function() {
            alert(i);
        });
    })(i);
}

for (var j = 0; j < 3; j++) {
    a[j]();
}
strager
  • 88,763
  • 26
  • 134
  • 176
  • 1
    Not that its required, but I think it looks cleaner, and describes your intentions better to wrap your "immediately executed" functions in `()` -> `(function(i){ ... })(i);` – gnarf Sep 12 '09 at 01:18
  • @gnarf, I was debating that myself. I guess it does make the intent clearer. I'll edit that in. – strager Sep 12 '09 at 01:20
  • this seems to side-step the original problem by offering an alternative solution which is not susceptible to the same underlying flaws... What you're doing here is pushing values onto an array. The original poster is pushing functions, which, we presume, are to be executed at some later time... – Funka Sep 12 '09 at 01:35
  • sorry, i commented too quickly. You are correct. Please also see my answer for a comparable solution to the same problem. – Funka Sep 12 '09 at 01:37
  • 1
    @funka - You still push functions: Creating the `(function(i){` scopes the variable `i` to stay whatever it is was the function is called. With `})(i);` you then immediately call that new function with `i` as a parameter, which returns a function... effectively storing `[function(){alert(0);},function(){alert(1);},function(){alert(2);}]` in `a` – gnarf Sep 12 '09 at 01:38
  • yes, i saw that right after i posted the comment. Both of our answers are scoping new variables to hold the current iteration of `i`, which both solve the same underlying problem. – Funka Sep 12 '09 at 01:41
6

Just another approach, using currying:

var a = [];
var f = function() {
    for (var i = 0; i < 3; i++) {
        a.push((function(a){alert(a);}).curry(i));
    }
    for (var j = 0; j < 3; j++) {
        a[j]();
    }
};

// curry implementation
Function.prototype.curry = function() {
  var fn = this, args = Array.prototype.slice.call(arguments);
  return function() {
    return fn.apply(this, args.concat(
      Array.prototype.slice.call(arguments)));
  };
};

Check the above snippet running here.

Christian C. Salvadó
  • 807,428
  • 183
  • 922
  • 838
1
var iterate = (function () {
    var i, j = [];
    for (i = 0; i < 3; i += 1) {
        j.push(i);
        alert(j[j.length - 1]);
    }
}());

You don't need closure to merely output a value. Your code should, however, be contained in a function for object-oriented containment. Functions don't have to be called to be executed.

  • "Functions don't have to be called to be executed." What? That statement isn't very clear, and as it is it sounds wrong. Clarify, please? – strager Sep 12 '09 at 01:13
  • In order to execute a function one of three things must happen. 1) The function must be called by name by something else that is executing. 2) The function can be inserted into a method, at which case the function can be anonymous and still be executed. I strongly object to using functions without giving them a name. 3) Functions can execute entirely on their own as they are interpreted if they are terminated with a parenthesis after their closing bracket, such as }(). That is called immediate evocation. –  Sep 12 '09 at 01:24
  • I disagree. A function is a value type, just like `42` or `'hello world'`, in Javascript. Whether or not it is assigned to a variable or used directly means nothing special. For an example of this behaviour, run: `(function(i) { var func = arguments.callee; if(!i) return; console.log('x'); window.setTimeout(function() { func(i - 1); }, 1000); })(4);` – strager Sep 12 '09 at 01:31
  • 2
    @austin: I couldn't disagree more with the statement "I strongly object to using functions without giving them a name" - anonymous functions are one of the most useful tools in the javascript ninja's repertoire. – gnarf Sep 12 '09 at 01:42
  • There are benefits to always naming your functions. 1) An anonymous function does the exact same thing as a function of immediate invocation except that can be used in few places. 2)A named function makes the writing of documentation significantly easier and more details. 3) Named functions provide a know place for unit testing where that some location is not uniquely know when using anonymous functions. As a result there are significant benefits to always naming functions and no benefits from not naming functions. –  Sep 12 '09 at 03:10
  • @strager No, a function is not a value it is an object. Objects have values as do arrays, variables, and host of other concepts. That does not mean an object is the same thing as a value merely because a value is contained. –  Sep 12 '09 at 03:12
  • @cheney, `42`, `'hello world'`, `{ }`, `new Array()`, and `function() { }` are all expressions which evaluate to a value. – strager Sep 12 '09 at 07:24
  • @strager Those may evaluate to a value, but that does not mean they are values. Perform a typeof() on a function and it will output "function", which is not a value type. You might want to brush up on JavaScript types. http://www.mozilla.org/js/language/js20-2000-07/libraries/types.html –  Sep 12 '09 at 10:08
  • @cheney, I find it funny that the link you gave in your last comment supports my argument. – strager Sep 13 '09 at 01:11
  • @strager your heuristic or mere lack of specificity indicates you have nothing credible to substantiate the subjectivity of the point. I gave you the link to illustrate that in JavaScript functions have their own type, as do values which you claim a function is. If I am in error then please indicate how so instead of inspiring guess work and nonsensical convolution. I suggest you read up about JavaScript types and the typeof function as you do not seem to understand how JavaScript operates as a language. –  Sep 13 '09 at 03:24
0

You can put the body of your loop in an anonymous function:

var a = [];

for(var i = 0; i < 3; i++) (function(i) {
    a.push(function() { alert(i); });
})(i)

for(var j = 0; j < 3; j++) {
    a[j]();
}

By creating that function and passing the loop's value of "i" as the argument, we are creating a new "i" variable inside the body of the loop that essentially hides the outer "i". The closure you push onto the array now sees the new variable, the value of which is set when the outer function function is called in the first loop. This might be clearer if we use a different name when we create the new variable... This does the same thing:

var a = [];

for(var i = 0; i < 3; i++) (function(iNew) {
    a.push(function() { alert(iNew); });
})(i)

for(var j = 0; j < 3; j++) {
    a[j]();
}

The value of "iNew" is assigned 0, then 1, then 2 because the function is called immediately by the loop.

Prestaul
  • 83,552
  • 10
  • 84
  • 84
-2

function(i){alert(i)

lovespring
  • 19,051
  • 42
  • 103
  • 153