JavaScript 闭包内部循环 – 简单的实际示例

JavaScript closure inside loops – simple practical example

提问人:nickf 提问时间:4/15/2009 最后编辑:3limin4t0rnickf 更新时间:5/15/2023 访问量:478269

问:

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

在 和 循环中也很明显:for infor of

const arr = [1,2,3];
const fns = [];

for (var i in arr){
  fns.push(() => console.log("index:", i));
}

for (var v of arr){
  fns.push(() => console.log("value:", v));
}

for (const n of arr) {
  var obj = { number: n }; // or new MyLibObject({ ... })
  fns.push(() => console.log("n:", n, "|", "obj:", JSON.stringify(obj)));
}

for(var f of fns){
  f();
}

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

JavaScript 循环闭

评论

59赞 Tomas Nikodym 9/14/2016
ES6 中,一个简单的解决方案是用 let 声明变量 i,该变量的作用域为循环主体。
4赞 Costa Michailidis 12/8/2016
JS 函数在声明时“关闭”了它们可以访问的范围,即使该范围中的变量发生变化,也保留对该范围的访问。上面数组中的每个函数都在全局范围内关闭(全局,仅仅是因为这恰好是它们声明的作用域)。稍后,将调用这些函数,记录全局范围内的最新值。这就是 JS : ) 而不是通过在每次循环运行时创建一个新作用域来解决这个问题,为每个函数创建一个单独的作用域来关闭。其他各种技术对额外的功能做同样的事情。iletvar

答:

422赞 Bjorn 4/15/2009 #1

尝试:

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 的答案是现在做这种事情的最佳方式。还有 lo-dash/underscore's 当你不需要或不想弄乱 's 时。_.partialbindthisArg

评论

6赞 aswzen 4/6/2018
关于 ?}(i));
4赞 Jet Blue 7/27/2018
@aswzen我认为它作为参数传递给函数。iindex
1赞 Abhishek Singh 3/15/2019
它实际上是在创建局部变量索引。
3赞 Eggs 4/14/2020
立即调用函数表达式,又名 IIFE。(i) 是匿名函数表达式的参数,该表达式立即被调用,索引从 i 开始设置。
2395赞 harto 4/15/2009 #2

好吧,问题在于每个匿名函数中的变量都绑定到函数外部的同一变量。i

ES6 解决方案:let

ECMAScript 6 (ES6) 引入了新的关键字,其作用域与基于 - 的变量不同。例如,在具有基于 - 的索引的循环中,通过循环的每次迭代都将有一个具有循环范围的新变量,因此您的代码将按预期工作。有很多资源,但我推荐 2ality 的块范围帖子作为一个很好的信息来源。letconstvarleti

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

但请注意,IE9-IE11 和 Edge 14 之前的 Edge 支持,但上述内容是错误的(它们不会每次都创建一个新的,因此上面的所有函数都会记录 3,就像我们使用Edge 14 终于做对了。letivar


ES5.1 解决方案:forEach

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

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

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

如果您碰巧在jQuery中工作,则该函数将为您提供类似的功能。$.each()


经典解决方案:闭合

您要做的是将每个函数中的变量绑定到函数外部的单独不变值:

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”的值保持预期。

评论

11赞 Incerteza 3/28/2014
不是仍然是闭包,因为它使用了变量?function createfunc(i) { return function() { console.log("My value: " + i); }; }i
62赞 Wladimir Palant 6/20/2014
不幸的是,这个答案已经过时了,没有人会在底部看到正确的答案 - 现在使用绝对是可取的,请参阅 stackoverflow.com/a/19323214/785541Function.bind()
97赞 cookie monster 7/12/2014
@Wladimir:你说的“正确答案”是不对的。他们每个人都有自己的位置。如果不绑定值,就无法绑定参数。此外,您还可以获得参数的副本,但无法在调用之间对其进行更改,这有时是必需的。因此,它们是完全不同的结构,更不用说实现历来很慢。当然,在简单的例子中,两者都行得通,但闭包是一个需要理解的重要概念,这就是问题所在。.bind().bind()thisi.bind()
11赞 Christian Landgren 2/7/2015
请停止使用这些 for-return 函数 hack,改用 [].forEach 或 [].map,因为它们避免重用相同的范围变量。
46赞 6/30/2015
@ChristianLandgren:这仅在迭代 Array 时才有用。这些技术不是“黑客”。它们是必不可少的知识。
10赞 jottos 4/15/2009 #3

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

80赞 eglasius 4/15/2009 #4

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

评论

0赞 eglasius 4/15/2009
@nickf哪个浏览器?正如我所说,它有兼容性问题,我的意思是严重的兼容性问题,就像我认为 IE 不支持 let 一样。
1赞 eglasius 4/16/2009
@nickf是的,请查看此参考:developer.mozilla.org/En/New_in_JavaScript_1.7 ...查看 let 定义部分,循环中有一个 onclick 示例
2赞 eglasius 4/16/2009
@nickf嗯,实际上您必须明确指定版本:<script type=“application/javascript;版本=1.7“/> ...由于 IE 的限制,我实际上没有在任何地方使用它,它只是不实用:(
0赞 eglasius 4/16/2009
您可以在此处查看浏览器对不同版本的支持 es.wikipedia.org/wiki/Javascript
98赞 Darren Clark 4/15/2009 #5

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

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

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

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

66赞 Boann 8/7/2012 #6

这是该技术的另一种变体,类似于 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

评论

0赞 midnite 12/3/2013
谢谢,您的解决方案有效。但我想问一下为什么这行得通,但是换线和行就行不通了?谢谢!varreturn
0赞 Boann 12/3/2013
@midnite 如果你交换了,那么在它返回内部函数之前,变量不会被赋值。varreturn
64赞 user1724763 4/20/2013 #7

这描述了在 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}objcounter1counter2

循环中的闭合

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

考虑:

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

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

评论

0赞 Charidimos 9/21/2019
小澄清:在循环闭包的第一个示例中,计数器[0]和计数器[1]不是独立的,不是因为性能原因。原因是在执行任何代码之前进行评估,如以下所述: MDN var:var 声明,无论它们发生在哪里,都会在执行任何代码之前进行处理。var obj = {counter: 0};
170赞 Ben McCormick 5/21/2013 #8

随着 ES6 的广泛支持,这个问题的最佳答案已经改变。ES6 为这种情况提供了 和 关键字。与其弄乱闭包,不如使用如下循环范围变量:letconstlet

var funcs = [];

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

val然后,将指向特定于该特定循环的对象,并将返回正确的值,而无需额外的闭包表示法。这显然大大简化了这个问题。

const类似于附加限制,即在初始赋值后不能将变量名称重新转换为新引用。let

现在,针对最新版本的浏览器的用户提供浏览器支持。/ 目前在最新的 Firefox、Safari、Edge 和 Chrome 中受支持。Node 也支持它,你可以利用 Babel 等构建工具在任何地方使用它。您可以在此处查看一个工作示例:http://jsfiddle.net/ben336/rbU4t/2/constlet

文档在这里:

但请注意,IE9-IE11 和 Edge 14 之前的 Edge 支持,但上述内容是错误的(它们不会每次都创建一个新的,因此上面的所有函数都会记录 3,就像我们使用Edge 14 终于做对了。letivar

评论

0赞 MattC 2/24/2016
不幸的是,“let”仍然没有得到完全支持,尤其是在移动设备中。developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/......
3赞 Dan 6/22/2016
截至 16 年 6 月, iOS Safari、Opera Mini 和 Safari 9 外,所有主要浏览器版本都支持 let。常青浏览器支持它。Babel 将正确转译它,以保持预期的行为,而无需开启高合规模式。
0赞 Ben McCormick 6/27/2016
@DanPantry,关于更新:)的时间更新以更好地反映事物的当前状态,包括添加对 const、文档链接和更好的兼容性信息的提及。
0赞 pixel 67 3/19/2018
这难道不是我们使用 babel 来转译代码的原因,以便不支持 ES6/7 的浏览器能够理解发生了什么吗?
53赞 Kemal Dağ 6/25/2013 #9

最简单的解决方案是,

而不是使用:

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_iii

此解决方案似乎适合任何此类问题,因为它需要对遭受此问题的原始代码进行最少的更改。事实上,这是设计使然,根本不是问题!

评论

2赞 DanMan 7/26/2013
在一本书中读过类似的东西。我也更喜欢这样,因为你不必接触你现有的代码(那么多),一旦你学会了自调用函数模式,你为什么要这样做就变得很明显了:将该变量捕获在新创建的范围内。
1赞 Kemal Dağ 7/26/2013
@DanMan谢谢。自调用匿名函数是解决 javascript 缺少块级变量范围的好方法。
3赞 jherax 10/27/2015
自调用或自调用不是这种技术的合适术语,IIFE(立即调用的函数表达式)更准确。参考: benalman.com/news/2010/11/...
34赞 yilmazburk 9/19/2013 #10

试试这个较短的

  • 无数组

  • 没有额外的 FOR 循环


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

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

http://jsfiddle.net/7P6EN/

评论

1赞 Kemal Dağ 6/28/2015
您的解决方案似乎输出正确,但它不必要地使用函数,为什么不只是控制台.log输出?最初的问题是关于创建具有相同闭包的匿名函数。问题是,由于它们只有一个闭包,因此每个闭包的 i 值都是相同的。我希望你明白了。
391赞 Aust 10/12/2013 #11

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

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

更新

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

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

评论

0赞 Bjorn 12/8/2014
这也是我这些天所做的,我也喜欢 lo-dash/underscore 的_.partial
20赞 6/28/2015
.bind()ECMAScript 6 功能将在很大程度上过时。此外,这实际上在每次迭代中创建两个函数。首先是匿名的,然后是生成的匿名的。更好的用途是在循环之外创建它,然后在循环内创建它。.bind().bind()
5赞 Aust 6/30/2015
@squint @mekdev - 你们俩都是对的。我最初的示例是快速编写的,以演示如何使用。我根据您的建议添加了另一个示例。bind
5赞 user2290820 9/11/2015
我认为与其在两个 O(n) 循环上浪费计算,不如为 (var i = 0; i < 3; i++) { log.call(this, i);
1赞 niry 1/8/2017
.bind() 执行公认的答案所建议的 PLUS 摆弄 .this
294赞 neurosnap 10/12/2013 #12

使用立即调用的函数表达式,这是将索引变量括起来的最简单、最易读的方法:

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

}

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

评论

11赞 Kyle Falconer 1/11/2014
为了进一步提高代码的可读性并避免混淆哪个是什么,我将函数参数重命名为 .iindex
5赞 Nico 11/30/2014
您将如何使用这种技术来定义原始问题中描述的数组函数
0赞 JLRishe 4/1/2015
@Nico 与原始问题中所示的方式相同,只是您将使用 .indexi
0赞 Nico 4/1/2015
@JLRishevar funcs = {}; for (var i = 0; i < 3; i++) { funcs[i] = (function(index) { return function() {console.log('iterator: ' + index);}; })(i); }; for (var j = 0; j < 3; j++) { funcs[j](); }
1赞 JLRishe 4/1/2015
@Nico 在 OP 的特殊情况下,他们只是在迭代数字,所以这不是一个很好的情况,但很多时候,当一个人从一个数组开始时,是一个不错的选择,比如:.forEach()forEach()var nums [4, 6, 7]; var funcs = {}; nums.forEach(function (num, i) { funcs[i] = function () { console.log(num); }; });
31赞 Travis J 3/6/2014 #13

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

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

在执行之前,该错误实际上不会发生。使用相同的逻辑,很明显,直到此时才收集 的值。一旦原始循环完成,其值将导致条件失败和循环结束。在这一点上,是和所以当被使用时,并被评估,它是 3 - 每次。funcs[someIndex]()ii++i3i < 3i3funcs[someIndex]()i

要克服这一点,您必须在遇到这种情况时进行评估。请注意,这已经以(其中有 3 个唯一索引)的形式发生。有几种方法可以捕获此值。一种是将其作为参数传递给函数,该函数已在此处以多种方式显示。ifuncs[i]

另一种选择是构造一个函数对象,该对象将能够关闭变量。这样可以做到这一点

jsFiddle Demo

funcs[i] = new function() {   
    var closedVariable = i;
    return function(){
        console.log("My value: " + closedVariable); 
    };
};
35赞 Daryl 5/3/2014 #14

下面是一个简单的解决方案,它使用(工作到IE9):forEach

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
16赞 wpding 7/14/2014 #15

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

  • 每个函数定义形成一个范围,由所有本地 声明的变量 和 。vararguments
  • 如果我们在另一个(外部)函数中定义了内部函数,则此 形成一个链,并将在执行过程中使用
  • 执行函数时,运行时通过搜索作用域链来评估变量。如果在链的某个点上可以找到一个变量,它将停止搜索并使用它,否则它会继续,直到到达属于 的全局范围。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

执行时,作用域链将为 。由于在 中找不到变量(既不使用参数声明,也不作为参数传递),因此它继续搜索,直到最终在全局范围内找到 的值,即 。funcsfunction inner -> globalifunction innervariwindow.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

执行时,现在作用域链将是 。这个时间可以在外部函数的作用域中找到,该作用域在 for 循环中执行 3 次,每次都正确绑定了值。它不会使用 internal 执行时的值。funcsfunction inner -> function outeriiwindow.i

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

评论

0赞 wpding 4/26/2017
我们很少真正编写此代码示例,但我认为它为理解基础知识提供了一个很好的示例。一旦我们了解了范围以及它们是如何链接在一起的,就更清楚地了解为什么其他“现代”方式(如自然)有效:传入的回调自然形成包装范围,并在 的每次迭代中正确绑定 el。因此,callback 中定义的每个内部函数都将能够使用正确的值Array.prototype.forEach(function callback(el) {})forEachel
11赞 Christian Landgren 12/10/2014 #16

令我惊讶的是,还没有人建议使用该函数来更好地避免(重新)使用局部变量。事实上,由于这个原因,我根本不再使用了。forEachfor(var i ...)

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

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

评论

3赞 JLRishe 4/1/2015
.forEach()如果你实际上没有映射任何东西,这是一个更好的选择,Daryl 在你发布前 7 个月就建议了这一点,所以没有什么可惊讶的。
0赞 jherax 10/27/2015
这个问题不是关于数组上的循环
0赞 Christian Landgren 11/12/2015
好吧,他想创建一个函数数组,这个例子展示了如何在不涉及全局变量的情况下做到这一点。
184赞 woojoo666 4/10/2015 #17

聚会有点晚了,但我今天在探讨这个问题,并注意到许多答案并没有完全解决 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]();
}

就像以前一样,每个内部函数都输出了分配给的最后一个值,现在每个内部函数只输出分配给 的最后一个值。但是每次迭代不应该有自己的吗?iilocalilocal

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

重要提示:JavaScript 没有块作用域。与块一起引入的变量的范围限定为包含的函数或脚本,并且设置它们的效果会持续到块本身之外。换言之,块语句不引入作用域。尽管“独立”块是有效的语法,但您不希望在 JavaScript 中使用独立块,因为如果您认为它们在 C 或 Java 中执行类似此类块的工作,它们不会执行您认为它们执行的操作。

重申强调:

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

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

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

这正是这个错误如此棘手的原因。即使你重新声明了一个变量,Javascript 也不会抛出错误,JSLint 甚至不会抛出警告。这也是为什么解决这个问题的最好方法是利用闭包,这本质上是这样的想法,即在 Javascript 中,内部函数可以访问外部变量,因为内部作用域“包围”了外部作用域。

Closures

这也意味着内部函数“保留”外部变量并使其保持活动状态,即使外部函数返回也是如此。为了利用这一点,我们创建并调用一个包装函数,纯粹是为了创建一个新作用域,在新作用域中声明,并返回一个内部函数,该函数使用(更多解释见下文):ilocalilocal

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

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

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

更新

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

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

看看现在是多么容易!有关更多信息,请参阅此答案,我的信息基于该答案

评论

8赞 12/27/2017
现在有这样的事情,即在 JavaScript 中使用 and 关键字进行块范围划分。如果这个答案扩大到包括这一点,在我看来,它将在全球范围内更加有用。letconst
0赞 woojoo666 3/2/2018
@TinyGiant确定的事情,我添加了一些信息并链接了更完整的解释let
0赞 nutty about natty 5/15/2018
@woojoo666 您的答案是否也适用于在循环中调用两个交替的 URL,如下所示: ?(可以将 window.open() 替换为 getElementById......)i=0; while(i < 100) { setTimeout(function(){ window.open("https://www.bbc.com","_self") }, 3000); setTimeout(function(){ window.open("https://www.cnn.com","_self") }, 3000); i++ }
0赞 woojoo666 6/4/2018
@nuttyaboutnatty这么晚的回复感到抱歉。示例中的代码似乎已经有效。您没有在超时函数中使用,因此不需要闭包i
0赞 woojoo666 6/8/2018
哎呀,对不起,意思是说“你示例中的代码似乎已经有效”
2赞 axelduch 5/7/2015 #18

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

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

4赞 Rune FS 6/17/2015 #19

您可以将声明性模块用于数据列表,例如 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的作者,因此偏向于使用它,所以不要把我的话作为对所述库的建议,只是为了声明性方法:)

评论

1赞 Rune FS 6/19/2015
我很想解释一下反对票。该代码解决了手头的问题。了解如何潜在地改进代码将很有价值
1赞 jherax 10/27/2015
什么?这不是此问题标签的一部分。此外,如果您使用第三方库,则可以提供文档的链接。Query.range(0,3)
1赞 Rune FS 10/27/2015
@jherax这些都是或当然是明显的改进。谢谢你的评论。我可以发誓已经有一个链接。如果没有这个帖子,我想:)毫无意义。我最初的想法是,我没有试图推动使用我自己的库,而是更多的声明性想法。但是,在 hinsight 中,我完全同意链接应该在那里
4赞 Rax Wunter 12/17/2015 #20

我更喜欢使用函数,它在创建伪范围时有自己的闭包: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
}

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

评论

0赞 Quentin 12/17/2015
更喜欢它而不是什么?这似乎是对其他答案的回复。它根本没有解决实际问题(因为你没有分配一个函数,以后在任何地方调用)。
0赞 Rax Wunter 12/17/2015
它与提到的问题完全相关:如何在没有闭包问题的情况下安全地迭代
0赞 Quentin 12/17/2015
现在,它似乎与公认的答案没有显着差异。
0赞 Rax Wunter 12/17/2015
不。在公认的答案中,建议使用“一些数组”,但我们在答案中处理一个范围,这完全是不同的东西,不幸的是在js中没有很好的解决方案,所以我的答案是尝试以一种良好和实践的方式解决问题
0赞 Rax Wunter 12/17/2015
@Quentin我建议在减去之前研究解决方案
4赞 pixel 67 5/5/2016 #21

还有另一种解决方案:与其创建另一个循环,不如将 绑定到 return 函数。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
}

通过绑定,也解决了这个问题。

9赞 Ali Kahoot 11/4/2016 #22

首先,了解这段代码有什么问题:

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
}

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

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

  1. 我们可以用 初始化或初始化一个新变量,并使其等于 。因此,在进行调用时,将使用,其作用域将在初始化后结束。而对于调用,将再次初始化:iletindexletiindexindex

    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]();                        
    }
    
3赞 Buksy 11/4/2016 #23

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

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?

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

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

因此,您的目标是首先保存 to function 的值,然后再将函数保存到 。例如,这可以通过以下方式完成:ifuncs

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
}

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

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

15赞 Prithvi Uppalapati 11/7/2016 #24

借助 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

评论

0赞 12/27/2017
const提供相同的结果,当变量的值不会更改时,应使用。但是,在Firefox中,for循环的初始值设定项内部的使用不正确,尚未得到修复。它不是在块内声明,而是在块外声明,这会导致对变量的重新声明,这反过来又会导致错误。在Firefox中正确实现了初始化器内部的使用,因此无需担心。constlet
0赞 Alexander Levakov 11/11/2016 #25

让我们利用新功能。因此,不再是闭包的变量,而只是文本的一部分:i

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

评论

3赞 lovasoa 11/12/2016
这很慢,可能不安全,并且并非在所有地方都有效。
25赞 Costa Michailidis 12/8/2016 #26

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]()
}

上面数组中的每个函数都在全局范围内关闭(全局,仅仅是因为这恰好是它们声明的作用域)。

稍后,将调用这些函数,记录全局范围内的最新值。这就是关闭的魔力和挫败感。i

“JavaScript 函数关闭了它们声明的范围,即使该范围内的变量值发生变化,也保留对该范围的访问。”

使用而不是通过在每次循环运行时创建一个新作用域,为每个要关闭的函数创建一个单独的作用域来解决此问题。其他各种技术对额外的功能做同样的事情。letvarfor

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]()
}

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

评论

1赞 Modermo 4/5/2017
在我读到这个答案之前,我很难理解这个概念。它触及了一个非常重要的点——将 的值设置为全局范围。当循环完成运行时,全局值现在为 3。因此,每当在数组中调用该函数时(例如,使用),该函数中的函数都会引用全局变量(即 3)。iforifuncs[j]ii
9赞 Vikash_Singh 1/20/2017 #27

使用闭包结构,这将减少额外的 for 循环。您可以在单个 for 循环中执行此操作:

var funcs = [];
for (var i = 0; i < 3; i++) {     
  (funcs[i] = function() {         
    console.log("My value: " + i); 
  })(i);
}
2赞 Pawel 2/28/2017 #28

许多解决方案似乎是正确的,但他们没有提到它被称为 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);
  }
}

查看不同浏览器的性能提升

评论

0赞 Pawel 12/27/2017
@TinyGiant 返回函数的示例仍在针对性能进行优化。我不会像所有 JavaScript 博主那样加入箭头函数的行列。它们看起来很酷很干净,但提倡以内联方式编写函数,而不是使用预定义的函数。在炎热的地方,这可能是一个不明显的陷阱。另一个问题是,它们不仅仅是语法糖,因为它们正在执行不必要的绑定,从而创建包装闭包。
2赞 12/27/2017
对未来读者的警告:这个答案错误地应用了“咖喱”一词。“讨好是指将一个将多个参数分解为一系列包含部分参数的函数。”此代码不执行任何此类操作。你在这里所做的就是从公认的答案中获取代码,移动一些东西,稍微改变一下样式和命名,然后称之为咖喱,它绝对不是。
2赞 jsbisht 7/17/2017 #29

计数器是原始的

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

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

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

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

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
-2赞 neatsu 10/13/2017 #30

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

let arr = [1,2,3];

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

arr.forEach(myFunc);
10赞 sidhuko 1/13/2018 #31

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

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

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

12赞 2 revs, 2 users 99%Bimal Das #32

我们将检查,当您一一声明时实际会发生什么。varlet

Case1使用 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 个函数消耗一次。您将看到一个名为 的属性。展开那个。你会看到一个 数组对象,展开该对象。您将找到一个声明到值为 3 的对象中的属性。[[Scopes]]"Global"'i'

enter image description here

enter image description here

结论:

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

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>

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

enter image description here

结论:

当你使用函数外部但在循环内部声明变量时,这个变量将不是一个全局变量 变量,它将成为一个级别变量,仅适用于同一函数。这就是为什么,我们 当我们调用函数时,每个函数的值都不同。'let'Blocki

有关Closer如何工作的更多详细信息,请浏览精彩的视频教程 https://youtu.be/71AtaJpJHw0

0赞 Brooks DuBois 2/25/2018 #33

虽然这个问题已经过时了,但我还有另一个相当有趣的解决方案:

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。这以某种方式及时刷新了 i 的状态,为您提供所需的结果。我这样做是偶然的,但考虑到之前的答案,这是有道理的。

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

注意:我不分享这个,因为我认为这是正确的答案。这是一个不稳定的解决方案,在某些情况下可能会破裂。实际上,我很惊讶它确实有效。

评论

0赞 nickf 3/16/2018
它之所以有效,是因为在第二个循环中,您正在覆盖函数中引用的相同内容。只要考虑一下,在整个代码段中,只有一个变量。它等同于:iii = 0; funcs[0](); i = 1; funcs[1](); ..
0赞 Brooks DuBois 3/16/2018
是的,考虑到关于范围界定的其他答案,这是有道理的,但仍然有点违反直觉
0赞 Ashish Kamble 10/11/2019
您正在覆盖 to 的值并立即使用该值进行调用,@nickf这是否意味着 goin to 也是i30,1,2,3j=0funcs[0]
0赞 Eyal Segal 3/14/2018 #34

假设您不使用 ES6; 您可以使用 IIFE:

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

但情况会有所不同。

0赞 Mark Manning 3/29/2018 #35

还行。我通读了所有的答案。尽管这里有一个很好的解释 - 我只是无法让它工作。于是我去网上找了看。https://dzone.com/articles/why-does-javascript-loop-only-use-last-value 的人有一个答案,这里没有介绍。所以我想我会发布一个简短的例子。这对我来说更有意义。

它的长处和短处是 LET 命令很好,但现在才被使用。但是,LET 命令实际上只是一个 TRY-CATCH 组合。这一直工作到IE3(我相信)。使用 TRY-CATCH 组合 - 生活简单而美好。这可能是 EMCScript 人员决定使用它的原因。它也不需要 setTimeout() 函数。所以没有时间浪费。基本上,每个 FOR 循环都需要一个 TRY-CATCH 组合。下面是一个示例:

    for( var i in myArray ){
       try{ throw i }
       catch(ii){
// Do whatever it is you want to do with ii
          }
       }

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

-2赞 stemon 5/25/2018 #36

为什么不在创建第一个(也是唯一一个)循环后立即调用每个函数,例如:

 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
    }

评论

2赞 nickf 5/31/2018
因为这只是问题的一个例子。
3赞 Ashish Yadav 7/13/2018 #37
var funcs = [];
for (var i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = function(param) {          // and store them in funcs
    console.log("My value: " + param); // each should log its value.
  };
}
for (var j = 0; j < 3; j++) {
  funcs[j](j);                      // and now let's run each one to see with j
}
0赞 user1559625 8/1/2018 #38

这证明了 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 编程语言那样在函数中设计像样的“静态”变量!

2赞 Nouman Dilshad 9/5/2018 #39

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

var 是函数范围的。

let 是块范围。

当你开始编写代码时,for 循环将迭代并将 i 的值赋值为 3,该值将在整个代码中保持 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
}
-1赞 swogger 4/8/2019 #40
  asyncIterable = [1,2,3,4,5,6,7,8];

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

评论

0赞 Federico Grandi 4/8/2019
最好在代码中添加一些解释,以便用户更好地理解它。试着解释为什么你的方式比其他答案中的方法更好:)
0赞 swogger 4/9/2019
谢谢@FedericoGrandi,在两页的例子之后,我认为这不是必需的。
4赞 B G Hari Prasad 4/15/2019 #41

使用 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]();                      
}

-2赞 Murtaza Hussain 4/17/2019 #42

在 的支持下,最好的方法是在这种确切的情况下使用 和 关键字。所以变量 get 和 在循环结束时,所有 ...的 的值都会更新,我们可以用来设置一个循环范围变量,如下所示:ES6letconstvarhoistediclosureslet

var funcs = [];
for (let i = 0; i < 3; i++) {          
    funcs[i] = function() {            
      console.log("My value: " + i); 
    };
}
7赞 Shivang Gupta 5/2/2019 #43

在 ES5 之前,这个问题只能使用闭包来解决。

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

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
}

7赞 CertainPerformance 9/22/2019 #44

如果您在循环而不是循环时遇到此类问题,例如:whilefor

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

收盘于当前值的技术略有不同。在块内声明一个块范围的变量,并为其分配电流。然后,无论在何处异步使用该变量,请替换为新的块范围变量:constwhileii

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

对于不支持块范围变量的旧浏览器,可以使用名为 的 IIFE:i

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

如果要调用的异步操作恰好如上所述,您还可以使用第三个参数进行调用,以指示要调用传递函数的参数:setTimeoutsetTimeout

var i = 0;
while (i < 5) {
  setTimeout(
    (thisIterationI) => { // Callback
      console.log(thisIterationI);
    },
    i * 1000, // Delay
    i // Gets passed to the callback; becomes thisIterationI
  );
  i++;
}

评论

0赞 3limin4t0r 9/23/2019
值得注意的是,该变量也可以使用 instead 来声明。两者都允许块范围的变量。letconst
0赞 Noman_1 2/12/2021
IIFE就是我一直在寻找的
2赞 perona chan 5/11/2023 #45

刚刚替换为varlet

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