views:

283

answers:

5

Hi,

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?

+4  A: 

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
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
@gnarf, I was debating that myself. I guess it does make the intent clearer. I'll edit that in.
strager
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
sorry, i commented too quickly. You are correct. Please also see my answer for a comparable solution to the same problem.
Funka
@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
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
+1  A: 
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
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.
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
@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
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.
@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.
@cheney, `42`, `'hello world'`, `{ }`, `new Array()`, and `function() { }` are all expressions which evaluate to a value.
strager
@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
@cheney, I find it funny that the link you gave in your last comment supports my argument.
strager
@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.
A: 

function(i){alert(i)

lovespring
More than likely, `i` will be undefined.
strager
+4  A: 

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.

CMS
Nice use of curry - although now I'm hungry....
gnarf
Wow. That's awesome. +1.
strager
A: 

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