1

I'm working on closure so hard, and I know that in the loop, new function refer closure using the last value of the iterator

so the following function's result is three times "item3 undefined"

function buildList(list) {
  var result = [];
  for (var i = 0; i < list.length; i++) {
    var item = 'item' + list[i];
    result.push( function() {alert(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]();
  }
} 

and then I know that anonymous function can induce scope, so I edit the first function as:

function buildList(list) {
  var result = [];
  for (var i = 0; i < list.length; i++) {
    (function(){
      var item = 'item' + list[i];
      result.push( function() {alert(item + ' ' + list[i])} );

    })();
  }
  return result;
}

but the result is "item1 undefined", "item2 undefined", "item3 undefined",

so my question is, why is the result still undefined after I using scope?

3 Answers3

0

You should pass the i to the anonymous function:

function buildList(list) {
  var result = [];
  for (var i = 0; i < list.length; i++) {
    (function(i){
      var item = 'item' + list[i];
      result.push( function() {alert(item + ' ' + list[i])} );

    })(i);
  }
  return result;
}
sroes
  • 14,663
  • 1
  • 53
  • 72
0

i still refers to a variable bound to the parent scope, not the one introduced by the FunctionExpression.

The correct fix is to bind a new variable to the new scope

function buildList(list) {
  var result = [];
  for (var i = 0; i < list.length; i++) {
    (function(i){
      var item = 'item' + list[i];
      result.push( function() {alert(item + ' ' + list[i])} );

    })(i);
  }
  return result;
}

Note the i being passed into the function.

Sean Kinsey
  • 37,689
  • 7
  • 52
  • 71
0

Assuming that the purpose of this code is just to learn; you create an anonymous function but you're still referring to the i in the previous scope, so you do not change anything from the first code you wrote initially; i still has the last value (list.length).

In order to avoid that, you need to have the current value of i in the scope of the function you've created:

function buildList(list) {
  var result = [];

  for (var i = 0; i < list.length; i++) {
    var item = 'item' + list[i];
    result.push(function (index) { 
      return function() {alert(item + ' ' + list[index])}
    }(i));
  }

  return result;
}

Alternatively you can use bind, to have partial application:

function buildList(list) {
  var result = [];

  for (var i = 0; i < list.length; i++) {
    var item = 'item' + list[i];
    result.push(function(index) {alert(item + ' ' + list[index])}.bind(null, i))
  }

  return result;
}

In ES6, or in Firefox with JS 1.8.5 enabled, you can use also let that declares block scope variable:

function buildList(list) {
  var result = [];

  for (var i = 0; i < list.length; i++) {
    var item = 'item' + list[i];
    let index = i;
    result.push(() =>alert(item + ' ' + list[index]));
  }

  return result;
}

In the last example there is also the ES6 arrow functions.

ZER0
  • 24,846
  • 5
  • 51
  • 54