1

我正在努力研究闭包,我知道在循环中,新函数使用迭代器的最后一个值引用闭包

所以以下函数的结果是“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]();
  }
} 

然后我知道匿名函数可以诱导作用域,所以我将第一个函数编辑为:

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;
}

但结果是“item1 undefined”、“item2 undefined”、“item3 undefined”、

所以我的问题是,为什么我使用范围后结果仍然未定义?

4

3 回答 3

0

您应该将 i 传递给匿名函数:

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;
}
于 2013-11-11T08:13:31.833 回答
0

i仍然指的是绑定到父作用域的变量,而不是 FunctionExpression 引入的变量。

正确的解决方法是将新变量绑定到新范围

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;
}

注意i被传递到函数中。

于 2013-11-11T08:13:45.370 回答
0

假设这段代码的目的只是为了学习;您创建了一个匿名函数,但您仍然引用i先前范围中的 ,因此您不会更改您最初编写的第一个代码的任何内容;i仍然有最后一个值 ( list.length)。

为了避免这种情况,您需要i在您创建的函数的范围内拥有当前值:

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;
}

或者,您可以使用bind进行部分应用:

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;
}

在 ES6 或启用 JS 1.8.5 的 Firefox 中,您也可以使用let来声明块范围变量:

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;
}

在最后一个示例中,还有ES6 箭头函数

于 2013-11-11T08:22:36.070 回答