In this example, list
, item
, and i
are being closed on. You are running into issues because the three are not evaluated inside of the function being pushed onto result
until the function is actually called.
This is actually a good example of why ES6 added the let
keyword as well, because using var
there is only 1 i
, and 1 item
variable in the buildList function that gets closed on, so all functions in the result
array point to the same i
and same item
variables, which, of course get changed in each iteration. It is spitting out undefined because it is trying to use the last value of i
that was set, which would be list.length
, which is obviously 1 more than the index of the last element in the list, so when it tries to access list[i]
it comes back as undefined.
Changing var i = ...
to let i = ...
in the for loop, and var item = ...
to let item = ...
fixes this and returns what you would expect.
function buildList(list) {
var result = [];
for (let i = 0; i < list.length; i++) {
let item = 'item' + i;
// close on i and list. i does not get evaluated until fn gets called,
// so we need to create a new i for each iteration of the loop, hence the use of let
result.push( function() {console.log(item + ' ' + list[i])} );
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
// Using j only to help prevent confusion -- could use I.
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList();
The way to do this without the use of ES6 let
is to create a function that will evaluate and close on i
and list
for each iteration of the loop manually, like so:
FYI: From a best-practice standpoint, this is actually the proper way to go so that you are not defining functions inside of a loop.
function logFunction (index, list) {
// inside here, index and list get evaluated as their current values in the loop
// and then closed on for that specific value in the function being returned
return function () {
console.log("item" + index + " " + list[index]);
}
}
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
// pass i and list to function to manually create closure
result.push( logFunction(i, list) );
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
// Using j only to help prevent confusion -- could use I.
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList(); //logs "item 2 is undefined" 3 times