循环内的 JavaScript 闭包——简单实用的例子 [英] JavaScript closure inside loops – simple practical example

查看:33
本文介绍了循环内的 JavaScript 闭包——简单实用的例子的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

My value: 3
My value: 3
My value: 3

而我希望它输出:

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

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


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


The same problem occurs when the delay in running the function is caused by using event listeners:

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>

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

… or asynchronous code, e.g. using 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 循环中也很明显:

It is also apparent in for in and for of loops:

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(var f of fns){
  f();
}

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

What’s the solution to this basic problem?

推荐答案

嗯,问题是变量 i,在你的每个匿名函数中,被绑定到同一个变量之外功能.

Well, the problem is that the variable i, within each of your anonymous functions, is bound to the same variable outside of the function.

ECMAScript 6 (ES6) 引入了新的 letconst 关键字,它们的作用域不同于基于 var 的变量.例如,在具有基于 let 索引的循环中,循环中的每次迭代都会有一个具有循环范围的新变量 i,因此您的代码将按预期工作.有很多资源,但我推荐 2ality 的块范围帖子作为重要的信息来源.

ECMAScript 6 (ES6) introduces new let and const keywords that are scoped differently than var-based variables. For example, in a loop with a let-based index, each iteration through the loop will have a new variable i with loop scope, so your code would work as you expect. There are many resources, but I'd recommend 2ality's block-scoping post as a great source of information.

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

但是请注意,Edge 14 之前的 IE9-IE11 和 Edge 支持 let 但会出现上述错误(它们不会每次都创建一个新的 i,所以上面的所有函数都会记录 3,就像我们使用 var 一样).Edge 14 终于做对了.

Beware, though, that IE9-IE11 and Edge prior to Edge 14 support let but get the above wrong (they don't create a new i each time, so all the functions above would log 3 like they would if we used var). Edge 14 finally gets it right.

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

With the relatively widespread availability of the Array.prototype.forEach function (in 2015), it's worth noting that in those situations involving iteration primarily over an array of values, .forEach() provides a clean, natural way to get a distinct closure for every iteration. That is, assuming you've got some sort of array containing values (DOM references, objects, whatever), and the problem arises of setting up callbacks specific to each element, you can do this:

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

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

The idea is that each invocation of the callback function used with the .forEach loop will be its own closure. The parameter passed in to that handler is the array element specific to that particular step of the iteration. If it's used in an asynchronous callback, it won't collide with any of the other callbacks established at other steps of the iteration.

如果您碰巧使用 jQuery,$.each() 函数可为您提供类似的功能.

If you happen to be working in jQuery, the $.each() function gives you a similar capability.

您想要做的是将每个函数内的变量绑定到函数外的一个单独的、不变的值:

What you want to do is bind the variable within each function to a separate, unchanging value outside of the function:

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"的值仍然如您所愿.

Since there is no block scope in JavaScript - only function scope - by wrapping the function creation in a new function, you ensure that the value of "i" remains as you intended.

这篇关于循环内的 JavaScript 闭包——简单实用的例子的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆