为了账号安全,请及时绑定邮箱和手机立即绑定

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

循环内的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.  };}for (var j = 0; j < 3; j++) {  funcs[j]();                      // and now let's run each one to see}它输出这个:我的价值:3 我的价值:3 我的价值:3而我希望它输出:我的价值:0 我的价值:1 我的价值:2使用事件侦听器导致运行函数的延迟时,会出现同样的问题:var buttons = document.getElementsByTagName("button");for (var i = 0; i < buttons.length; i++) {          // let's create 3 functions  buttons[i].addEventListener("click", function() { // as event listeners    console.log("My value: " + i);                  // each should log its value.  });}<button>0</button><br><button>1</button><br><button>2</button>...或异步代码,例如使用Promises:// Some async wait functionconst wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));for(var i = 0; i < 3; i++){  wait(i * 100).then(() => console.log(i)); // Log `i` as soon as each promise resolves.}这个基本问题的解决方案是什么?
查看完整描述

5 回答

?
拉风的咖菲猫

TA贡献1995条经验 获得超2个赞

好吧,问题是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

}

由于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)引入了新的let和const关键字,其范围与var基于变量的不同。例如,在具有let基于索引的循环中,循环中的每次迭代都将具有一个新值,i其中每个值都在循环中作用域,因此您的代码将按预期工作。有很多资源,但我建议将2ality的区块范围发布作为一个很好的信息来源。


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

    funcs[i] = function() {

        console.log("My value: " + i);

    };

}

但要注意,IE9-IE11和Edge在Edge 14支持之前let却出现了上述错误(它们i每次都没有创建新的,所以上面的所有函数都会像我们使用的那样记录3 var)。Edge 14最终做对了。


查看完整回答
反对 回复 2019-05-20
?
胡说叔叔

TA贡献1804条经验 获得超8个赞

尝试:

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


查看完整回答
反对 回复 2019-05-20
?
莫回无

TA贡献1865条经验 获得超7个赞

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

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


查看完整回答
反对 回复 2019-05-20
?
沧海一幻觉

TA贡献1824条经验 获得超5个赞

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

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

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

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


查看完整回答
反对 回复 2019-05-20
  • 5 回答
  • 0 关注
  • 1123 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信