提问





var funcs = [];
for (var i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = function() {          // and store them in funcs
    console.log("My value: " + i); // each should log its value.
  };
}
for (var j = 0; j < 3; j++) {
  funcs[j]();                      // and now let's run each one to see
}



好吧,问题是你的每个匿名函数中的变量i被绑定到函数之外的同一个变量。


经典解决方案:闭包



你想要做的是将每个函数中的变量绑定到函数之外的一个单独的,不变的值:




var funcs = [];

function createfunc(i) {
    return function() { console.log("My value: " + i); };
}

for (var i = 0; i < 3; i++) {
    funcs[i] = createfunc(i);
}

for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}



尝试:


var funcs = [];

for (var i = 0; i < 3; i++) {
    funcs[i] = (function(index) {
        return function() {
            console.log("My value: " + index);
        };
    }(i));
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}


修改(2014):


就个人而言,我认为@Aust最近关于使用.bind的答案是现在做这种事情的最好方法。当你不这样做的时候,还有低调/强调_.partial需要或想要混淆binds thisArg

最佳参考


还没有提到的另一种方法是使用Function.prototype.bind [174]




var funcs = {};
for (var i = 0; i < 3; i++) {
  funcs[i] = function(x) {
    console.log('My value: ' + x);
  }.bind(this, i);
}
for (var j = 0; j < 3; j++) {
  funcs[j]();
}



使用立即调用的函数表达式,这是封装索引变量的最简单和最易读的方法:[175]


for (var i = 0; i < 3; i++) {

    (function(index) {
        console.log('iterator: ' + index);
        //now you can also loop an ajax call here 
        //without losing track of the iterator value: $.ajax({});
    })(i);

}


这将迭代器i发送到我们定义为index的匿名函数中。这将创建一个闭包,其中保存变量i以供以后在IIFE中的任何异步功能中使用。

其它参考1


派对迟到了,但我今天正在探讨这个问题并注意到许多答案并没有完全解决Javascript如何处理范围,这基本上归结为这个问题。


正如许多其他人提到的那样,问题是内部函数引用了相同的i变量。那么为什么我们不是每次迭代都创建一个新的局部变量,而是让内部函数引用呢?




//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    var ilocal = i; //create a new local variable
    funcs[i] = function() {
        console.log("My value: " + ilocal); //each should reference its own local variable
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}



随着ES6现在得到广泛支持,这个问题的最佳答案已经改变。 ES6为这种确切的情况提供letconst关键字。我们可以使用let来设置一个循环范围变量,而不是乱搞闭包:


var funcs = [];
for (let i = 0; i < 3; i++) {          
    funcs[i] = function() {            
      console.log("My value: " + i); 
    };
}


val然后将指向一个特定于循环的特定转弯的对象,并且将返回正确的值而不使用额外的闭合符号。这显然简化了这个问题。


const类似于let,另外一个限制是变量名称在初始赋值后不能反弹到新的引用。


现在,浏览器支持针对最新版本的浏览器。最新的Firefox,Safari,Edge和Chrome目前支持const/let。 Node也支持它,你可以利用像Babel这样的构建工具在任何地方使用它。你可以在这里看到一个有效的例子:http://jsfiddle.net/ben336/rbU4t/2/[176]


文件在这里:



  • 常量




但要注意,IE9-IE11和Edge之前的Edge 14支持let但是得到了上述错误(他们每次都不会创建新i,所以上面的所有函数都会记录3他们会使用var)。边缘14最终得到它。[177] [178]

其它参考2


另一种说法是函数中的i在执行函数时被绑定,而不是创建函数的时间。


创建闭包时,i是对外部作用域中定义的变量的引用,而不是创建闭包时的副本。它将在执行时进行评估。


大多数其他答案提供了通过创建另一个不会为您改变价值的变量来解决的方法。


我只是想为了清晰起见添加一个解释。对于一个解决方案,我个人而言,我会选择Harto,因为从这里的答案来看这是最不言自明的方式。任何发布的代码都可行,但我选择关闭工厂而不必编写一堆注释来解释为什么我要声明一个新变量(Freddy和1800)或者有奇怪的嵌入式闭包语法(apphacker)。

其它参考3


你需要了解的是javascript中变量的范围是基于函数的。这是一个重要的区别,而不是c#,你有块范围,只是将变量复制到for内的一个将起作用。


将它包装在一个函数中,将函数评估为像apphacker的答案那样返回函数,这样做就可以了,因为变量现在具有函数范围。


还有一个let关键字而不是var,允许使用块范围规则。在这种情况下,在for中定义一个变量就可以了。也就是说,由于兼容性,let关键字不是一个实用的解决方案。


var funcs = {};
for (var i = 0; i < 3; i++) {
    let index = i;          //add this
    funcs[i] = function() {            
        console.log("My value: " + index); //change to the copy
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        
}

其它参考4


这是该技术的另一个变体,类似于Bjorn的(apphacker),它允许您在函数内部分配变量值,而不是将其作为参数传递,有时可能更清晰:


for (var i = 0; i < 3; i++) {
    funcs[i] = (function() {
        var index = i;
        return function() {
            console.log("My value: " + index);
        }
    })();
}


请注意,无论使用何种技术,index变量都会变成一种静态变量,绑定到内部函数的返回副本。即,在调用之间保留对其值的更改。它可以非常方便。

其它参考5


这描述了在JavaScript中使用闭包的常见错误。


一个函数定义一个新环境



考虑:


function makeCounter()
{
  var obj = {counter: 0};
  return {
    inc: function(){obj.counter ++;},
    get: function(){return obj.counter;}
  };
}

counter1 = makeCounter();
counter2 = makeCounter();

counter1.inc();

alert(counter1.get()); // returns 1
alert(counter2.get()); // returns 0


每次调用makeCounter时,{counter: 0}会导致创建一个新对象。另外,obj的新副本
也被创建以引用新对象。因此,counter1counter2彼此独立。


循环中的闭包



在循环中使用闭包很棘手。


考虑:


var counters = [];

function makeCounters(num)
{
  for (var i = 0; i < num; i++)
  {
    var obj = {counter: 0};
    counters[i] = {
      inc: function(){obj.counter++;},
      get: function(){return obj.counter;}
    }; 
  }
}

makeCounters(2);

counters[0].inc();

alert(counters[0].get()); // returns 1
alert(counters[1].get()); // returns 1


请注意,counters[0]counters[1] 不独立。事实上,它们的运作方式相同obj!


这是因为在循环的所有迭代中只有一个obj副本,可能是出于性能原因。
即使{counter: 0}在每次迭代中创建一个新对象,obj的相同副本也会更新为
引用最新的对象。


解决方案是使用另一个辅助函数:


function makeHelper(obj)
{
  return {
    inc: function(){obj.counter++;},
    get: function(){return obj.counter;}
  }; 
}

function makeCounters(num)
{
  for (var i = 0; i < num; i++)
  {
    var obj = {counter: 0};
    counters[i] = makeHelper(obj);
  }
}


这是有效的,因为函数作用域中的局部变量以及函数参数变量都是分配的
入境时的新副本。


有关详细讨论,请参阅JavaScript闭包陷阱和用法[179]

其它参考6


最简单的解决方案是,


而不是使用:


var funcs = [];
for(var i =0; i<3; i++){
    funcs[i] = function(){
        alert(i);
    }
}

for(var j =0; j<3; j++){
    funcs[j]();
}


提醒2,共3次。这是因为在for循环中创建的匿名函数共享相同的闭包,并且在该闭包中,i的值是相同的。使用它来防止共享关闭:


var funcs = [];
for(var new_i =0; new_i<3; new_i++){
    (function(i){
        funcs[i] = function(){
            alert(i);
        }
    })(new_i);
}

for(var j =0; j<3; j++){
    funcs[j]();
}


这背后的想法是,用IIFE(立即调用的函数表达式)封装for循环的整个主体,并将new_i作为参数传递并将其捕获为i。由于匿名函数是立即执行的,因此i值对于匿名函数内定义的每个函数都是不同的。[180]


这个解决方案似乎适合任何这样的问题,因为它需要对遇到此问题的原始代码进行最小的更改。事实上,这是设计,它应该不是一个问题!

其它参考7


尝试这个较短的




  • 没有数组

  • 没有额外的循环







for (var i = 0; i < 3; i++) {
    createfunc(i)();
}

function createfunc(i) {
    return function(){console.log("My value: " + i);};
}


http://jsfiddle.net/7P6EN/[181]

其它参考8


OP显示的代码的主要问题是直到第二个循环才会读取i。为了演示,想象一下在代码中看到错误


funcs[i] = function() {            // and store them in funcs
    throw new Error("test");
    console.log("My value: " + i); // each should log its value.
};


直到funcs[someIndex]执行()才会发生错误。使用相同的逻辑,显然在此之前也不会收集i的值。一旦原始循环结束,i++i带到3的值,这导致条件i < 3失败并且循环结束。此时,i3,所以当使用funcs[someIndex]()时,i被评估,每次都是3。


为了解决这个问题,您必须在遇到问题时评估i。请注意,这已经以funcs[i]的形式发生(其中有3个唯一索引)。有几种方法可以捕获此值。一种是将其作为参数传递给一个函数,该函数已经以几种方式显示在这里。


另一个选择是构造一个能够关闭变量的函数对象。这样就可以实现


jsFiddle Demo [182]


funcs[i] = new function() {   
    var closedVariable = i;
    return function(){
        console.log("My value: " + closedVariable); 
    };
};

其它参考9


这是一个使用forEach的简单解决方案(回到IE9):


var funcs = {};
[0,1,2].forEach(function(i) {          // let's create 3 functions
    funcs[i] = function() {            // and store them in funcs
        console.log("My value: " + i); // each should log its value.
    };
})
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}


打印:



My value: 0
My value: 1
My value: 2


其它参考10


JavaScript函数关闭它们在声明时可以访问的范围,并保留对该范围的访问权限,即使该范围中的变量发生更改。




var funcs = []

for (var i = 0; i < 3; i += 1) {
  funcs[i] = function () {
    console.log(i)
  }
}

for (var k = 0; k < 3; k += 1) {
  funcs[k]()
}



在阅读了各种解决方案之后,我想补充一点,这些解决方案的工作原理是依赖于范围链的概念。这是JavaScript在执行过程中解析变量的方式。



  • 每个函数定义形成一个由所有本地组成的范围
    var及其arguments声明的变量。

  • 如果我们在另一个(外部)函数中定义了内部函数,那么这个
    形成一个链,并将在执行期间使用

  • 执行函数时,运行时通过搜索范围链来评估变量。如果可以在链的某个点找到变量,它将停止搜索并使用它,否则它将一直持续到达到属于window的全局范围。



在初始代码中:


funcs = {};
for (var i = 0; i < 3; i++) {         
  funcs[i] = function inner() {        // function inner's scope contains nothing
    console.log("My value: " + i);    
  };
}
console.log(window.i)                  // test value 'i', print 3


执行funcs时,范围链将为function inner -> global。由于变量ifunction inner中找不到(既没有使用var声明也没有作为参数传递),它继续搜索,直到最终找到i的值全球范围是window.i


通过将它包装在外部函数中,可以显式定义像harto那样的辅助函数,也可以使用像Bjorn那样的匿名函数:​​]]


funcs = {};
function outer(i) {              // function outer's scope contains 'i'
  return function inner() {      // function inner, closure created
   console.log("My value: " + i);
  };
}
for (var i = 0; i < 3; i++) {
  funcs[i] = outer(i);
}
console.log(window.i)          // print 3 still


funcs执行时,现在范围链将是function inner -> function outer。这个时间i可以在外部函数的范围中找到,它在for循环中执行3次,每次都有值i正确绑定。它不会使用window.i的值]]当内在执行时。


更多细节可以在这里找到

它包括在循环中创建闭包的常见错误,以及我们为什么需要关闭和性能考虑。[185]

其它参考11


借助ES6的新功能,可以管理块级别范围:


var funcs = [];
for (let i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function() {            // and store them in funcs
        console.log("My value: " + i); // each should log its value.
    };
}
for (let j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}


OP问题中的代码用 let 代替 var [186]

其它参考12


我很惊讶没有人建议使用forEach函数来更好地避免(重新)使用局部变量。实际上,由于这个原因,我根本不再使用for(var i ...)


[0,2,3].forEach(function(i){ console.log('My value:', i); });
// My value: 0
// My value: 2
// My value: 3


//编辑使用forEach而不是地图。

其它参考13


首先,了解这段代码的错误:


var funcs = [];
for (var i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function() {            // and store them in funcs
        console.log("My value: " + i); // each should log its value.
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}


这里正在初始化funcs[]数组时,i正在递增,funcs数组被初始化,func数组的大小变为3,所以i = 3,
现在当调用funcs[j]()时,它再次使用变量i,该变量已经增加到3。


现在要解决这个问题,我们有很多选择。以下是其中两个:



  1. 我们可以用let初始化i或用let初始化一个新变量index并使其等于i。因此,当进行调用时,将使用index并且其范围将在初始化之后结束。对于呼叫,index将再次初始化:


    var funcs = [];
    for (var i = 0; i < 3; i++) {          
        let index = i;
        funcs[i] = function() {            
            console.log("My value: " + index); 
        };
    }
    for (var j = 0; j < 3; j++) {
        funcs[j]();                        
    }
    

  2. 其他选项可以引入tempFunc,返回实际功能:


    var funcs = [];
    function tempFunc(i){
        return function(){
            console.log("My value: " + i);
        };
    }
    for (var i = 0; i < 3; i++) {  
        funcs[i] = tempFunc(i);                                     
    }
    for (var j = 0; j < 3; j++) {
        funcs[j]();                        
    }
    


其它参考14


这个问题确实展示了JavaScript的历史!现在我们可以避免使用箭头函数进行块作用域,并使用Object方法直接从DOM节点处理循环。




const funcs = [1, 2, 3].map(i => () => console.log(i));
funcs.map(fn => fn())




  我们将检查,当您声明varlet时会发生什么
  逐个。



案例1 :使用var



<script>
   var funcs = [];
   for (var i = 0; i < 3; i++) {
     funcs[i] = function () {
        debugger;
        console.log("My value: " + i);
     };
   }
   console.log(funcs);
</script>


现在按 F12 打开 Chrome控制台窗口并刷新页面。
在数组中扩展每3个函数。您将看到一个名为[**Scopes**]的属性。展开那个。你会看到一个
数组对象名为"Global",展开那一个。您将在对象中声明属性'i',其值为3。


[187]


[188]


结论:



  1. 当您在函数外使用'var'声明变量时,它将变为全局变量(您可以通过键入i
    window.i在控制台窗口中。它将返回3)。

  2. 您声明的不良函数除非您调用,否则不会调用并检查函数内部的值
    功能。

  3. 调用该函数时,console.log("My value: " + i)从其Global对象中获取值并显示
    结果



CASE2:使用let



现在用'let'替换'var'


<script>
    var funcs = [];
    for (let i = 0; i < 3; i++) {
        funcs[i] = function () {
           debugger;
           console.log("My value: " + i);
        };
    }
    console.log(funcs);
</script>


做同样的事情,转到范围。现在您将看到两个对象"Block""Global"。现在展开Block对象,你
会看到i在那里被定义,奇怪的是,对于每个函数,如果i的值不同(0,1,2)。


[189]


结论:


当您使用'let'声明变量时,即使在函数外部但在循环内部,此变量也不是全局变量
变量,它将成为Block级变量,仅适用于相同的函数。这就是原因,我们
当我们调用函数时,每个函数的i值都不同。


有关近距离工作的详细信息,请浏览精彩的视频教程https://youtu.be/71AtaJpJHw0 [190]

其它参考15


我更喜欢使用forEach函数,它有自己的闭包创建一个伪范围:


var funcs = [];

new Array(3).fill(0).forEach(function (_, i) { // creating a range
    funcs[i] = function() {            
        // now i is safely incapsulated 
        console.log("My value: " + i);
    };
});

for (var j = 0; j < 3; j++) {
    funcs[j](); // 0, 1, 2
}


这看起来比其他语言的范围更丑,但恕我直言比其他解决方案更怪异。

其它参考16


原始示例不起作用的原因是您在循环中创建的所有闭包都引用了相同的帧。实际上,在一个对象上只有一个i变量有3个方法。他们都打印出相同的价值。

其它参考17


您可以使用声明模块来存储数据列表,例如query-js(*)。在这些情况下,我个人认为声明方法不那么令人惊讶[191]


var funcs = Query.range(0,3).each(function(i){
     return  function() {
        console.log("My value: " + i);
    };
});


然后,您可以使用第二个循环并获得预期结果,或者您可以这样做


funcs.iterate(function(f){ f(); });


(*)我是query-js的作者,因此偏向于使用它,所以不要仅仅因为声明性方法而把我的话作为推荐用于所述库:)

其它参考18


还有另一种解决方案:只需将this绑定到返回函数,而不是创建另一个循环。




var funcs = [];

function createFunc(i) {
  return function() {
    console.log('My value: ' + i); //log value of i.
  }.call(this);
}

for (var i = 1; i <= 5; i++) {  //5 functions
  funcs[i] = createFunc(i);     // call createFunc() i=5 times
}



许多解决方案似乎都是正确的,但是他们并没有提到它Currying,这是一种针对这种情况的函数式编程设计模式。比绑定速度快3-10倍,具体取决于浏览器。[192]


var funcs = [];
for (var i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = curryShowValue(i);
}
for (var j = 0; j < 3; j++) {
  funcs[j]();                      // and now let's run each one to see
}

function curryShowValue(i) {
  return function showValue() {
    console.log("My value: " + i);
  }
}


查看不同浏览器的性能增益。[193]

其它参考19


你的代码不起作用,因为它的作用是:


Create variable `funcs` and assign it an empty array;  
Loop from 0 up until it is less than 3 and assign it to variable `i`;
    Push to variable `funcs` next function:  
        // Only push (save), but don't execute
        **Write to console current value of variable `i`;**

// First loop has ended, i = 3;

Loop from 0 up until it is less than 3 and assign it to variable `j`;
    Call `j`-th function from variable `funcs`:  
        **Write to console current value of variable `i`;**  
        // Ask yourself NOW! What is the value of i?


现在的问题是,调用函数时变量i的值是多少?因为第一个循环是以i < 3的条件创建的,所以当条件为假时它会立即停止,因此它是i = 3


您需要了解,在创建函数时,没有执行任何代码,只会保存以供日后使用。因此,当稍后调用它们时,解释器会执行它们并询问:i的当前值是多少?


所以,你的目标是首先将i的值保存到函数中,然后才将函数保存到funcs。这可以通过以下方式完成:


var funcs = [];
for (var i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function(x) {            // and store them in funcs
        console.log("My value: " + x); // each should log its value.
    }.bind(null, i);
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}


这样,每个函数都有自己的变量x,我们在每次迭代中将x设置为i的值。


这只是解决此问题的多种方法之一。

其它参考20


使用闭包结构,这会减少你的额外for循环。你可以在一个for循环中完成:[194]


var funcs = [];
for (var i = 0; i < 3; i++) {     
  (funcs[i] = function() {         
    console.log("My value: " + i); 
  })(i);
}

其它参考21


反对原始


让我们定义回调函数如下:


// ****************************
// COUNTER BEING A PRIMITIVE
// ****************************
function test1() {
    for (var i=0; i<2; i++) {
        setTimeout(function() {
            console.log(i);
        });
    }
}
test1();
// 2
// 2


超时完成后,将为两者打印2。这是因为回调函数根据词法范围访问该值,其中定义了函数。


要在定义回调时传递和保留值,我们可以创建一个闭包,以在调用回调之前保留该值。这可以按如下方式完成:[196]


function test2() {
    function sendRequest(i) {
        setTimeout(function() {
            console.log(i);
        });
    }

    for (var i = 0; i < 2; i++) {
        sendRequest(i);
    }
}
test2();
// 1
// 2


现在对此有什么特别之处原语通过值传递并复制。因此,当定义闭包时,它们保持前一循环的值。


反对一个对象


由于闭包可以通过引用访问父函数变量,因此这种方法与基元的方法不同。


// ****************************
// COUNTER BEING AN OBJECT
// ****************************
function test3() {
    var index = { i: 0 };
    for (index.i=0; index.i<2; index.i++) {
        setTimeout(function() {
            console.log('test3: ' + index.i);
        });
    }
}
test3();
// 2
// 2


因此,即使为作为对象传递的变量创建了闭包,也不会保留循环索引的值。这是为了表明不会复制对象的值,而是通过引用访问它们。


function test4() {
    var index = { i: 0 };
    function sendRequest(index, i) {
        setTimeout(function() {
            console.log('index: ' + index);
            console.log('i: ' + i);
            console.log(index[i]);
        });
    }

    for (index.i=0; index.i<2; index.i++) {
        sendRequest(index, index.i);
    }
}
test4();
// index: { i: 2}
// 0
// undefined

// index: { i: 2}
// 1
// undefined

其它参考22


这是异步代码经常遇到的问题,变量i是可变的,并且在进行函数调用时,将执行使用i的代码并且i将发生变异到它的最后一个值,这意味着在循环中创建的所有函数都将创建一个闭包,i将等于3(for循环的上限+ 1。[197]


解决此问题的方法是创建一个函数,该函数将为每次迭代保存i的值并强制复制i(因为它是一个原语,如果它可以帮助您将其视为快照)。