bpoinder85
bpoinder85

Reputation: 35

What part of this example is a closure?

This link has examples on closures in JavaScript.

In example 5, we have this code:

function buildList(list) {
  var result = [];
  for (var i = 0; i < list.length; i++) {
    var item = 'item' + i;
    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(); //logs "item 2 is undefined" 3 times

In the description of the problem, it is stated "Note that when you run the example, "item2 undefined" is alerted three times! This is because just like previous examples, there is only one closure for the local variables for buildList."

What part of the above code is the closure? I'm being thrown off by the example and the explanation because I can't identify what part is being closed, and thus, I cannot figure out why the code results in undefined.

Upvotes: 0

Views: 80

Answers (3)

Ernesto Schiavo
Ernesto Schiavo

Reputation: 1060

The closure is:

function() {console.log(item + ' ' + list[i])}

When the array is built, the i passed is a reference to the i variable outside of the closure function scope. The i variable is always the same and is incrementing (because of the for loop) so, when you call each closure function it tries to access to list[list.length] (because i becomes 2 inside the loop which is from 0 to list.length-1) therefore you got an undefined exception.

Upvotes: 1

mhodges
mhodges

Reputation: 11116

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

Upvotes: 2

sirrocco
sirrocco

Reputation: 8055

This would be the closure: function() {console.log(item + ' ' + list[i])} because it's using i and other variables from the parent scope.

What happens when you run the code ?

Step: 1. push first function, which has access to parent scope - i === 0

Step: 2. push second function - same thing -> i === 1 ( this is the same i as for step 1. - so now both functions have i === 1

...

Step: (list.length-1). push list.length - 1 function in the array -> now i === list.length-1 (but this is the same i to which all closures have access to.

One last step is i++ from the for.

Now you start calling the functions you created but they all have reference to the same i which now is equal to list.length and when it accesses list[list.length] it's outside the bounds of the array so it's undefined.

Upvotes: 1

Related Questions