This happens because your function (closure) maintains a
rather than
- a snapshot of
i
as it existed during each particular iteration.
This means that your function, when executed, will know the current value of i
at that instant. Because the loop will already have finished at that point, that will be the last value of i
set by the loop. So, how do we get the snapshot value instead of the reference? Luckily, numeric parameters are passed by value... so, you can avoid your issue by passing i
to a second function...(fiddle):
function fun1(){
var result = [];
for (var i = 0; i < 5; i++){
(function(x){
result.push(function() {
return x; // x now has i's snapshotted value
});
})(i); // passes i by value since it's numeric
}
return result;
}
alert(fun1()[0]())
[Edit] Let's take the loop out of the equation (fiddle):
var i = 1;
var fn = function() { // this closure maintains a reference to i
alert(i);
};
i = 10; // such that when we change i's value here
fn(); // it's reflected by the function call: alerting 10
So, to "fix" this (fiddle):
var i = 1;
var fn = (function(x) { // this is a self-executing anonymous function
return function() { // this closure maintains a reference to x
alert(x);
};
})(i); // we're passing i by value such that x will retain that value
i = 10; // when we change i here, it has no affect on x
fn(); // this will alert 1
What a question!
The problem is, in javascript, as in java, integer values are passed by value, not by reference. Which is what everyone else here is telling you. But i'm assuming you don't know what this means. It is pretty standard across languages, so let's see if i can explain!
NOTE: this is a crippled explanation. I don't really know how javascript is passing the values, and fun terms like "stack" and "heap" and "pass by reference value" (which i think is technically correct?) can pop up in more academic answers. Hopefully i can skip all that, and assume that
- there is one place where the computer stores memory for programs (there isn't)
- it is stored in hex notation on RAM (maybe it does? i don't know)
- we only pass-by-reference and pass-by-value (more on that... now!)
So, we have pass-by-reference and pass-by-value.
In the program's memory, when passing by value, the thing that the variable "i" is pointing to is a literal number.
When you pass by reference, the thing that the variable "i" would point to would be the memory location of some other object.
What does this mean?
line 1. i = new Object();
line 2. i = new Object();
after line 1. the program will make a little home in memory for "i", and it will give that a locatiion - i don't know, some ram value (say. i don't really know the under-the-hood javascript). Then, in that value, it will put another value. So we have:
i -> 0x67 -> 0x68
so the program knows that, when it sees the value "i", it goes to memory location 0x67, and gets the value 0x68. It also knows that THAT value is pointing to another memory location. In which case we have
...
0x67 -> 0x68 (in bytes)
0x68 -> some byte representation of a new Object()
0x69 -> i don't know! some more bytes. they're not important right now.
...
After line 2. runs, we'd have
i ->0x67 -> 0x69 (in bytes)
and
0x69 -> some byte representation of a new Object()
to give:
...
0x67 -> 0x68 (in bytes)
0x68 -> some byte representation of a new Object()
0x69 -> some byte representation of a new Object()
...
Now, as it happens, if you were to so an equality on the two new Ojbects, you'd fine they're the same ( i think ).
What is important to note here, is that after the second line, the value of i has changed, from the first memory location, to the second.
SO, if you did this:
i = new Object()
j = i
i = new Object()
you would get:
i -> 0x67 -> 0x68 ->byte for a new Object
j -> 0x69 -> 0x68 -> byte for a new Object
i -> 0x67 -> 0x70 -> byte for another new Object. Same bytes as above.
See? "j" gets the actual byte values in "i", which means "j" gets the value 0x68, which is the memory location for a new object. After the third line, j has the location of the first object, and "i" has the location of the second object.
HOWEVER, any changes you make to j - say
j.x = "moo"
won't appear in the object that "i" is pointing to. It will only appear in "j" object. Because, remember, j is pointing to a totally different object in a totally different location from the object i is pointing to.
Ok, with that out of the way, let's swing back to pass-by-value.
Here
i = 6;
j = i;
i = 7;
so we get
i -> 0x67 -> 6 (well, the bytes for 6, anyway)
j -> 0x68 -> 6 (again, bytes for 6. the same bytes. We're pointing to i here)
i -> 0x67 -> 7
AND, if we were to look at j?
j -> 0x68 -> 6
so, because we updated "i", we do not also update "j", get it? The bytes are being held, j get its own copy of the bytes here.
OKAY, so with all the above out of the way, let's check out your code:
function fun1(){
var result = [];
for (var i = 0; i < 5; i++){
result.push( function() {return i} );
}
return result;
}
right, so in the for loop, you are making a function that holds a reference to i, and the action of the for loop is to increment i.
You're probably going "hold up!" or something - do you think internally like that? - because i just noted how the data is passed by value for integers (or numbers, generally). Problem here is this:
From this page:
https://developer.mozilla.org/en/JavaScript/Guide/Closures
A closure is a special kind of object that combines two things: a function, and the environment in which that function was created. The environment consists of any local variables that were in-scope at the time that the closure was created.
So... and i pasted the above for a reason - a closure is an object that is a function (fun1, say) and the environment in which the function was created. And the environment? It consists of the local variables (ooh, how about i?) in scope.
So, to be blunt... just as i is still in scope the entire time of that for loop, that same "i" is in scope for each and every closure we generate. And that i? that i is getting its value updated. You're not passing the snapshot of i, you're passing, as near as i can determine, the reference object itself. It is no longer pass-by-value, we're kinda sorta back to pass by reference.
So, in memory, we have
i -> 0x67 -> 0
i -> 0x67 -> 1
i -> 0x67 -> 2
i -> 0x67 -> 3
i -> 0x67 -> 4
i -> 0x67 -> 5 (yeah, the loop increments i, then fails the test of i <5. but is still 5)
in your array, you're putting a function that just spits back the value of i. But you're incrementing i all the time. Each function you put in that array is going to spit out "5".
To take it one step further...
function fun1(){
var result = [];
for (var i = 0; i < 5; i++){
result.push( function() {return i++;} );
}
return result;
}
alert(fun1()[0]());
alert(fun1()[0]());
alert(fun1()[4]());
Here, we're testing to see if we can "keep" changes outside the initial environment. If you run this, you won't see any changes stored. In the first example, we increment "i" to 6 - but when we run the example again, i is again 5 - the increment was not kept.
Finally, trying on another array function shows the value wasn't incremented there either.
So, for you, what you want is to somehow freeze in time the value of i at the moment it enters the array
And that's pretty tricky.
function fun1(){
var result = [];
for (var i = 0; i < 5; i++){
result.push( eval("(function() { return " + i+";})"));
}
return result;
}
Where "eval" is a fancy javascript function that creates code to be run at the time it is run - dynamically, i mean. And i surround it by parenthesis because
Why does JavaScript's eval need parentheses to eval JSON data?
I don't know a super amount about eval though!
Next up, one of the other solutions shows this:
(function(x){
result.push( function() {return x} );
})(i);
which is placed within the context of the for loop - to give
function fun1(){
var result = [];
for (var i = 0; i < 5; i++){
(function(x){
result.push( function() {return x} );
})(i);
}
return result;
}
What this does is create a new environment for the closure to exist within. And in this environment, "x" is fixed forever and ever as the value that i currently (ie at point of instantiation) is. So when our array tries to access a given closure, it exists within a new environment where x is fixed. If we'd done this instead:
function fun1(){
var result = [];
for (var i = 0; i < 5; i++){
(function(){
result.push( function() {return i;} );
})();
}
return result;
}
Then we'd run into the same problem. The "i" still exists in the environment (or scope, if you will), of the loop, so it is going to get picked up, no matter how many times you stick it in functions.
If anyone reads this and thinks what i've written is bollocks, please let me know! I'm kinda keen to hear how js does memory management stuff.