You fell victim to the closure monster. Nothing to be afraid of, you're not the first and will not be the last. The closure monster is always hungry :)
Your solution is not working because the unnamed functions stored in your array of objects is always referencing the live value of i (which will be 10 at the end of the loop).
The way to avoid this in JS is to create a different function context for each value of b.
The usual way to do so is to create a "factory" function returning the actual function invokation that returns the proper b value.
function b_factory (b)
{
return function ()
{
return 'end'+b;
}
}
Notice how the returned function references a b
variable that is outside its scope.
It is this ability to provide a value for variables outside the scope of a given function that is called a lexical closure.
In this case, the closure lies in b_factory
's parameter.
Now to use this:
function test() {
array = [];
for (i = 0; i < 10; i += 1) {
array.push(
{a:'start' + i, b:b_factory(i)}
);
}
return array;
}
What happens here is:
- the unnamed function returned by
b_factory
is referenced by array[i].b
, so it is not destroyed by the garbage collector
- since the
b
parameter of b_factory
is in turn referenced by the unnamed function, the execution context of b_factory (where b
is to be found) is also kept alive in memory.
- the value of
b
as seen from inside the unnamed function is the value of the parameter passed to b_factory
, i.e. the value of i
at the time of the call to b_factory
.
Once you get familiar with this, you can dispense with the creation of a factory function and use an unnamed function to provide the closure, like so:
array.push(
{a:'start' + i, b:function(b) {return function() { return 'end'+b;}}(i)}
Note that in this case, the inner unnamed function is a bit of an overkill. Since the b field of your object will be a constant for the rest of the execution, your unnamed function has nothing at all to compute (everything is constant seen from its context). It is about as useful as defining an array of functions like these:
function give_me_one () { return 1; }
function give_me_two () { return 2; }
// ;)
You could define your b field as a string and use this code instead:
array.push(
{a:'start' + i, b:function(b) { return 'end'+b; }(i)}
Here the unnamed function still creates the same closure, but returns a constant string instead of a function. Since there is no persisting object to reference the b parameter, the closure gets garbage-collected immediately after the unnamed function execution.
The b field is now a string, so you should access it without the brackets, like so:
console.log(test()[5].a + ' ' + test()[5].b);
Of course this is just an exercise to demonstrate the use of closures. The most efficient way of initializing your object is just to handle b as you did handle a, i.e.
array.push(
{a:'start'+i, b:'end'+i}