循环内的JavaScript闭包 - 简单实用的例子

时间:2009-04-15 06:06:20

标签: javascript loops closures

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

输出:

  

我的价值:3
  我的价值:3
  我的价值:3

我希望输出:

  

我的价值:0
  我的价值:1
  我的价值:2


当使用事件侦听器导致运行函数的延迟时,会出现同样的问题:

var buttons = document.getElementsByTagName("button");
// let's create 3 functions
for (var i = 0; i < buttons.length; i++) {
  // as event listeners
  buttons[i].addEventListener("click", function() {
    // each should log its value.
    console.log("My value: " + i);
  });
}
<button>0</button>
<br />
<button>1</button>
<br />
<button>2</button>

...或异步代码,例如使用Promises:

// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));

for (var i = 0; i < 3; i++) {
  // Log `i` as soon as each promise resolves.
  wait(i * 100).then(() => console.log(i));
}

这个基本问题的解决方案是什么?

44 个答案:

答案 0 :(得分:1987)

嗯,问题是每个匿名函数中的变量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++) {
  // and now let's run each one to see
  funcs[j]();
}

由于JavaScript中没有块范围 - 只有函数范围 - 通过将函数创建包装在新函数中,可以确保“i”的值保持不变。


2015解决方案:forEach

随着Array.prototype.forEach函数的相对广泛的可用性(2015年),值得注意的是,在那些主要涉及一组值的迭代的情况下,.forEach()提供了一种干净,自然的获取方式每次迭代都有一个独特的闭包。也就是说,假设你有某种类型的数组包含值(DOM引用,对象,等等),并且设置特定于每个元素的回调问题,你可以这样做:

var someArray = [ /* whatever */ ];
// ...
someArray.forEach(function(arrayElement) {
  // ... code code code for this one element
  someAsynchronousFunction(arrayElement, function() {
    arrayElement.doSomething();
  });
});

这个想法是每次调用与.forEach循环一起使用的回调函数都是它自己的闭包。传递给该处理程序的参数是特定于该迭代的特定步骤的数组元素。如果它在异步回调中使用,它将不会与在迭代的其他步骤中建立的任何其他回调冲突。

如果您正好在jQuery中工作,$.each()函数会为您提供类似的功能。


ES6解决方案:let

ECMAScript 6(ES6)引入了新的letconst关键字,其范围与基于var的变量不同。例如,在具有基于let的索引的循环中,循环中的每次迭代都将具有新值i,其中每个值都限定在循环内,因此您的代码将按预期工作。有很多资源,但我建议2ality's block-scoping post作为一个很好的信息来源。

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

请注意,IE9-IE11和Edge 14之前的Edge支持let但是出现上述错误(他们每次都不会创建新的i,所以上面的所有函数都会log 3就像我们使用var时那样。 Edge 14最终做对了。

答案 1 :(得分:362)

尝试:

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的more recent answer about using .bind是现在做这种事情的最佳方法。当你不需要或想要混淆_.partial的{​​{1}}时,还会有短划线/下划线bind

答案 2 :(得分:334)

尚未提及的另一种方法是使用Function.prototype.bind

&#13;
&#13;
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]();
}
&#13;
&#13;
&#13;

<强>更新

正如@squint和@mekdev所指出的那样,通过首先在循环外创建函数然后在循环中绑定结果,可以获得更好的性能。

&#13;
&#13;
function log(x) {
  console.log('My value: ' + x);
}

var funcs = [];

for (var i = 0; i < 3; i++) {
  funcs[i] = log.bind(this, i);
}

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

答案 3 :(得分:254)

使用Immediately-Invoked Function Expression,最简单,最易读的方法来封装索引变量:

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中的任何异步功能中使用。

答案 4 :(得分:150)

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

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

&#13;
&#13;
//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]();
}
&#13;
&#13;
&#13;

就像之前一样,每个内部函数输出分配给i的最后一个值,现在每个内部函数只输出分配给ilocal的最后一个值。但是,每次迭代都不应该拥有它自己的ilocal

事实证明,这就是问题所在。每次迭代都共享相同的范围,因此在第一次迭代之后的每次迭代都只是覆盖ilocal。来自MDN

  

重要说明:JavaScript没有块范围。使用块引入的变量的范围限定为包含函数或脚本,并且设置它们的效果将持续超出块本身。换句话说,块语句不引入范围。虽然&#34;独立&#34;块是有效的语法,您不希望在JavaScript中使用独立块,因为如果您认为它们在C或Java中执行类似块的操作,则它们不会执行您认为的操作。

重申强调:

  

JavaScript没有块范围。使用块引入的变量的范围限定为包含函数或脚本

我们可以通过在每次迭代中声明它之前检查ilocal来看到这一点:

&#13;
&#13;
//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++) {
  console.log(ilocal);
  var ilocal = i;
}
&#13;
&#13;
&#13;

这正是这个bug如此棘手的原因。即使您重新声明变量,Javascript也不会抛出错误,JSLint甚至不会发出警告。这也是为什么解决这个问题的最好方法是利用闭包,这本质上是在Javascript中,内部函数可以访问外部变量,因为内部范围&#34;包含&#34;外部范围。

Closures

这也意味着内部功能&#34;坚持&#34;外部变量并保持活动,即使外部函数返回。为了利用这一点,我们创建并调用一个包装器函数纯粹是为了创建一个新的范围,在新范围中声明ilocal,并返回一个使用ilocal的内部函数(下面有更多解释):

&#13;
&#13;
//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++) {
    funcs[i] = (function() { //create a new scope using a wrapper function
        var ilocal = i; //capture i into a local var
        return function() { //return the inner function
            console.log("My value: " + ilocal);
        };
    })(); //remember to run the wrapper function
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}
&#13;
&#13;
&#13;

在包装函数中创建内部函数为内部函数提供了一个只有它才能访问的私有环境,一个&#34; closure&#34;。因此,每次我们调用包装函数时,我们都会创建一个新的内部函数,它具有独立的环境,确保ilocal变量不会相互碰撞和覆盖。一些小的优化给出了许多其他SO用户给出的最终答案:

&#13;
&#13;
//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++) {
    funcs[i] = wrapper(i);
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}
//creates a separate environment for the inner function
function wrapper(ilocal) {
    return function() { //return the inner function
        console.log("My value: " + ilocal);
    };
}
&#13;
&#13;
&#13;

<强>更新

现在ES6成为主流,我们现在可以使用新的let关键字来创建块范围的变量:

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

var funcs = {};
for (let i = 0; i < 3; i++) { // use "let" to declare "i"
    funcs[i] = function() {
        console.log("My value: " + i); //each should reference its own local variable
    };
}
for (var j = 0; j < 3; j++) { // we can use "var" here without issue
    funcs[j]();
}
&#13;
&#13;
&#13;

看看它现在有多容易!有关详细信息,请参阅this answer,我的信息基于。{/ p>

答案 5 :(得分:137)

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

var funcs = [];

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

然后

val将指向一个特定于该循环的特定转向的对象,并且将返回正确的值而不使用额外的闭包表示法。这显然简化了这个问题。

constlet类似,但附加限制是变量名称在初始分配后无法重新绑定到新引用。

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

文档:

请注意,IE9-IE11和Edge 14之前的Edge支持let但是出现上述错误(他们每次都不会创建新的i,所以上面的所有函数都会log 3就像我们使用var时那样。 Edge 14最终做对了。

答案 6 :(得分:84)

另一种说法是,函数中的i在执行函数时受到约束,而不是创建函数的时间。

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

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

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

答案 7 :(得分:67)

你需要了解的是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]();
}

答案 8 :(得分:55)

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

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

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

答案 9 :(得分:51)

这描述了在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 closure pitfalls and usage

答案 10 :(得分:48)

最简单的解决方案是,

而不是使用:

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值都不同。

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

答案 11 :(得分:30)

尝试这个较短的

  • 没有数组

  • 没有额外的循环


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

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

http://jsfiddle.net/7P6EN/

答案 12 :(得分:25)

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

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

答案 13 :(得分:24)

这是一个使用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

答案 14 :(得分:22)

JavaScript函数&#34;关闭&#34;他们在声明时可以访问的范围,并保留对该范围的访问权限,即使该范围中的变量发生变化。

&#13;
&#13;
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]()
}
&#13;
&#13;
&#13;

上面数组中的每个函数都关闭全局范围(全局,只是因为它恰好是它们声明的范围)。

稍后调用这些函数,在全局范围内记录i的最新值。这是关闭的神奇和挫折。

&#34; JavaScript函数关闭它们声明的范围,并保留对该范围的访问权限,即使该范围内的变量值发生更改。&#34;

使用let代替var通过每次for循环运行时创建新范围来解决此问题,为每个要关闭的函数创建一个单独的范围。各种其他技术使用额外的功能做同样的事情。

&#13;
&#13;
var funcs = []

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

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

let使变量块作用域。块用花括号表示,但在for循环的情况下,初始化变量i在我们的例子中,被认为是在括号中声明的。)

答案 15 :(得分:13)

在阅读各种解决方案之后,我想补充一点,这些解决方案的工作原理是依靠范围链的概念。它是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。由于在i中找不到变量function 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的值。

可以找到更多细节here
它包括在循环中创建闭包的常见错误,就像我们在这里一样,以及为什么我们需要闭包和性能考虑。

答案 16 :(得分:13)

通过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

答案 17 :(得分:9)

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

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

&#13;
&#13;
const buttons = document.getElementsByTagName("button");
Object
  .keys(buttons)
  .map(i => buttons[i].addEventListener('click', () => console.log(i)));
&#13;
<button>0</button><br>
<button>1</button><br>
<button>2</button>
&#13;
&#13;
&#13;

答案 18 :(得分:8)

我很惊讶没有人建议使用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代替地图。

答案 19 :(得分:7)

  

我们会检查,当您声明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。

enter image description here

enter image description here

结论:

  1. 当您在函数外使用'var'声明变量时,它将变为全局变量(您可以通过键入i或 控制台窗口中的window.i。它将返回3)。
  2. 您声明的不良函数不会调用并检查函数内部的值,除非您调用 功能。
  3. 调用该函数时,console.log("My value: " + i)从其Global对象中获取值并显示 结果
  4. CASE2:使用let

    现在将'var'替换为'let'

    <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对象,你 会看到&#39;我&#39;在那里被定义,奇怪的是,对于每个函数,i的值是不同的(0,1,2)。

    enter image description here

    <强>结论:

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

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

答案 20 :(得分:7)

首先,了解此代码的错误:

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,所以{{ 1}}。 现在,当i = 3,被调用时,它再次使用变量funcs[j](),该变量已经增加到3。

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

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

    index
  2. 其他选项可以是引入一个返回实际函数的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](); }

    tempFunc

答案 21 :(得分:6)

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

答案 22 :(得分:5)

使用closure结构,这会减少额外的for循环。你可以在一个for循环中完成它:

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

答案 23 :(得分:3)

style.color

答案 24 :(得分:3)

我更喜欢使用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
}

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

答案 25 :(得分:2)

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

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

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

因此,您的目标是首先将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的值。

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

答案 26 :(得分:2)

使用let(blocked-scope)代替var。

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

答案 27 :(得分:2)

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

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

请参阅the performance gain in different browsers

答案 28 :(得分:2)

还有另一个解决方案:只需将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
}

通过绑定 ,也可以解决问题。

答案 29 :(得分:2)

您可以将声明模块用于query-js(*)等数据列表。在这些情况下,我个人认为声明方法不那么令人惊讶

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

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

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

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

答案 30 :(得分:1)

反对原始

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

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

超时完成后,两者都会打印2。这是因为回调函数基于lexical scope访问该值,其中定义了函数。

要在定义回调时传递并保留值,我们可以创建一个closure,以便在调用回调之前保留该值。这可以按如下方式完成:

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

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

现在有什么特别之处是&#34;基元通过值传递并复制。因此,当定义闭包时,它们保持前一循环的值。&#34;

反对一个对象

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

// ****************************
// 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

答案 31 :(得分:1)

例如,如果您在while循环而不是for循环中遇到这种问题,例如:

var i = 0;
while (i < 5) {
  setTimeout(function() {
    console.log(i);
  }, i * 1000);
  i++;
}

关闭当前值的技术有些不同。在const块中用while声明一个块范围的变量,并为其分配当前i。然后,无论该变量在哪里异步使用,都将i替换为新的块作用域变量:

var i = 0;
while (i < 5) {
  const thisIterationI = i;
  setTimeout(function() {
    console.log(thisIterationI);
  }, i * 1000);
  i++;
}

对于不支持块作用域变量的旧版浏览器,可以使用以i调用的IIFE:

var i = 0;
while (i < 5) {
  (function(innerI) {
    setTimeout(function() {
      console.log(innerI);
    }, innerI * 1000);
  })(i);
  i++;
}

答案 32 :(得分:1)

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

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

答案 33 :(得分:1)

只需将var关键字更改为let。

var是函数范围的。

let是块作用域的。

当您开始编写代码时,for循环将迭代并将i的值赋给3,在整个代码中,i的值仍为3。我建议您阅读有关节点(let,var,const和其他)范围的更多信息

funcs = [];
for (let i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] =async function() {          // and store them in funcs
    await 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
}

答案 34 :(得分:0)

确定。我仔细阅读了所有的答案。即使这里有一个很好的解释 - 我也无法让它发挥作用。所以我去了网上看。 https://dzone.com/articles/why-does-javascript-loop-only-use-last-value的人有一个答案,这里没有提到。所以我想我会发一个简短的例子。这对我来说更有意义。

它的长短是LET命令很好但现在才被使用。但是,LET命令实际上只是一个TRY-CATCH组合。这一直可以回到IE3(我相信)。使用TRY-CATCH组合 - 生活简单而美好。可能是为什么EMCScript人员决定使用它。它也不需要setTimeout()函数。所以没有时间丢失。基本上,每个FOR循环需要一个TRY-CATCH组合。这是一个例子:

lookupCoords()

如果您有多个FOR循环,则只需为每个循环放置一个TRY-CATCH组合。另外,就个人而言,我总是使用我正在使用的FOR变量的双字母。所以“ii”代表“我”等等。我在例程中使用此技术将鼠标悬停命令发送到不同的例程。

答案 35 :(得分:0)

我们假设您不使用es6; 您可以使用IFFY功能:

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

但它会有所不同。

答案 36 :(得分:0)

这证明了JavaScript在“关闭”和“非关闭”的工作方式上多么丑陋。

在以下情况下:

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

funcs [i]是全局函数,并且为'console.log(“ My value:” + i);'正在打印全局变量i

对于

var funcs = [];

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

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

因为这种扭曲的javascript封闭设计“ console.log(“我的值:” + i);“从外部函数'createfunc(i)'中打印i

全部是因为javascript无法在像C编程语言正在执行的功能中设计像'static'变量这样的东西!

答案 37 :(得分:0)

虽然这个问题很老并且已经回答了,但我还有另一个相当有趣的解决方案:

var funcs = [];

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

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

变化太小,几乎很难看出我做了什么。我将第二个迭代器从j切换到了i。这会以某种方式及时刷新我的状态以给你想要的结果。我偶然做到了这一点,但考虑到之前的答案,这是有道理的。

我写这篇文章指出这个小但非常重要的区别。希望有助于消除像我这样的其他学习者的困惑。

注意:我没有分享这个,因为我认为这是正确的答案。这是一个可能会在某些情况下破裂的平台解决方案。实际上,我非常惊讶它确实有用。

答案 38 :(得分:0)

  asyncIterable = [1,2,3,4,5,6,7,8];

  (async function() {
       for await (let num of asyncIterable) {
         console.log(num);
       }
    })();

答案 39 :(得分:0)

ES6的支持下,最好的方法是在这种情况下使用letconst关键字。因此var变量将获得hoisted,并且在循环结束时,i的值将为所有closures ...更新,我们可以只使用let设置像这样的循环作用域变量:

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

答案 40 :(得分:0)

直到ES5,只能使用关闭解决此问题。

但是现在在ES6中,我们有了块级范围变量。在第一个 for循环中将 var 更改为 let 将解决该问题。

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 (var j = 0; j < 3; j++) {
  funcs[j]();                      // and now let's run each one to see
}

答案 41 :(得分:-1)

为什么不在创建它们之后简单地在第一个(也是唯一的)循环中调用每个函数,例如:

 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.
    };
    funcs[i]();// and now let's run each one to see
    }

答案 42 :(得分:-2)

让我们利用new Function。因此,i将停止为closure的变量,并成为文本的一部分:

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

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

答案 43 :(得分:-3)

这个问题已经有很多有效的答案。然而,使用功能方法的人并不多。以下是使用forEach方法的替代解决方案,该方法适用于回调和闭包:

let arr = [1,2,3];

let myFunc = (val, index) => { console.log('val: '+val+'\nindex: '+index); };

arr.forEach(myFunc);