This situation is, in fact, quite easy to explain and it occurs in many programming languages, not just JavaScript.
Let's first take the output sequense a,b,c,c,c,c
and discard the first three elements - a,b,c
because this is what gets printed when you execute your function f()
inside the first for
loop.
So, we're left with the sequense c,c,c
which gets printed when we iterate over the fs
array. The reason why you don't get a,b,c
printed is because the x
parameter that you have inside your first for
loop is captured by reference (not sure if the definition is valid, though) by your f()
closure (remember, every function in JavaScript is a closure of some sort).
Now, since the console.log(x)
expression is evaluated when you call the f()
, the current value of the captured x
parameter will be passed as an argument. While the code works, as seems to be expected, when you invoke f()
inside the first for
loop, the x
is what has just been assigned from arr[i]
so you get a,b,c
. But when you exit the loop, the x
(even though it's unavailable to the outer scope) is still captured by f()
but after the first loop it has a value c
(the one that it got on the last iteration) and that is what gets evaluated when you iterated over your functions the second time.
This is, in fact, can be a source of many confusing bugs and so some languages can detect this and notify the developer (for example, C# compiler, when it sees constructs like this, produces the following warning: Access to modified closure
).
To fix the problem, you just need to capture the current value of x
inside your first for
loop:
for (var i in arr) {
var x = arr[i];
var f = (function(x) {
return function() { console.log(x) };
})(x);
f();
fs.push(f);
}
Here we use IIFE (Immediately-Invoked Function Expression) to capture the current value of x
.
Hope this helps.