var函数=[];//让我们创建3个函数对于(变量i=0;i<3;i++){//并将其存储在funcs中funcs[i]=函数(){//每个都应该记录其值。console.log(“我的值:”,i);};}对于(变量j=0;j<3;j++){//现在让我们逐一查看函数[j]();}

它输出以下内容:

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

而我希望它输出:

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


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

var buttons=document.getElementsByTagName(“button”);//让我们创建3个函数for(var i=0;i<buttons.length;i++){//作为事件侦听器buttons[i].addEventListener(“单击”,函数(){//每个都应该记录其值。console.log(“我的值:”,i);});}<按钮>0</按钮><br/><button>1</button><br/><按钮>2</按钮>

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

//一些异步等待函数const wait=(ms)=>new Promise((解析,拒绝)=>setTimeout(解析,ms));对于(变量i=0;i<3;i++){//一旦每个承诺得到解决,就记录“i”。等待(i*100)。然后(()=>console.log(i));}

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

常量arr=[1,2,3];常量fns=[];用于(arr中的变量i){fns.push(()=>console.log(“index:”,i));}对于(arr的var v){fns.push(()=>console.log(“值:”,v));}for(arr常量){var obj={number:n};//或新的MyLibObject({…})fns.push(()=>console.log(“n:”,n,“|”,“obj:”,JSON.stringify(obj)));}for(fns的var f){f();}

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


当前回答

我很惊讶还没有人建议使用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而不是map。

其他回答

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

每个函数定义形成一个范围,该范围由所有本地var声明的变量及其参数。如果我们在另一个(外部)函数中定义了内部函数形成一条链,并将在执行过程中使用当函数被执行时,运行时通过搜索作用域链来评估变量。如果在链的某个点上可以找到一个变量,它将停止搜索并使用它,否则将继续搜索,直到到达属于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时,作用域链将是函数inner->global。由于在函数内部找不到变量i(既没有使用var声明,也没有作为参数传递),因此它继续搜索,直到最终在全局范围(即window.i)中找到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时,现在作用域链将是函数inner->函数outer。这一次,可以在for循环中执行3次的外部函数的作用域中找到i,每次都有正确绑定的值i。当内部执行时,它不会使用window.i的值。

更多详情请点击此处它包括在循环中创建闭包的常见错误,以及我们需要闭包的原因和性能考虑。

JavaScript函数在声明时“关闭”它们有权访问的作用域,并保留对该作用域的访问权,即使该作用域中的变量发生变化。

var函数=[]对于(变量i=0;i<3;i+=1){funcs[i]=函数(){控制台日志(i)}}对于(var k=0;k<3;k+=1){函数[k]()}

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

稍后调用这些函数,记录全局范围中i的最新值。这就是结束的魔力和挫折。

“JavaScript函数在声明它们的作用域上关闭,并保留对该作用域的访问权,即使该作用域内的变量值发生变化。”

使用let而不是var可以解决这个问题,方法是在每次for循环运行时创建一个新的作用域,为每个要关闭的函数创建一个单独的作用域。其他各种技术对额外的功能也有同样的作用。

var函数=[]for(设i=0;i<3;i+=1){funcs[i]=函数(){控制台日志(i)}}对于(var k=0;k<3;k+=1){函数[k]()}

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

好的。我通读了所有答案。尽管这里有一个很好的解释,但我还是没能让它发挥作用。所以我去上网了。位于的人员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”等等。我在一个例程中使用这种技术将鼠标悬停命令发送到另一个例程。

您需要了解的是javascript中变量的范围是基于函数的。这与c#有一个重要的区别,因为c#有块作用域,只需将变量复制到for中的一个即可。

将其包装在一个函数中,该函数会像apphacker的答案那样计算返回函数的值,这会起到作用,因为变量现在具有函数作用域。

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

var funcs={};对于(变量i=0;i<3;i++){设索引=i//添加此funcs[i]=函数(){console.log(“我的值:”+索引)//更改副本};}对于(变量j=0;j<3;j++){函数[j]();}

只需将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
}