简体   繁体   English

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

[英]JavaScript closure inside loops – simple practical example

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

It outputs this:它输出这个:

My value: 3我的价值:3
My value: 3我的价值:3
My value: 3我的价值:3

Whereas I'd like it to output:而我想拨打 output:

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


The same problem occurs when the delay in running the function is caused by using event listeners: function由于使用事件监听导致运行延迟也会出现同样的问题:

 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, eg using Promises: …或异步代码,例如使用 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)); }

It is also apparent in for in and for of loops: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(); }

What's the solution to this basic problem?这个基本问题的解决方案是什么?

Well, the problem is that the variable i , within each of your anonymous functions, is bound to the same variable outside of the function.好吧,问题在于每个匿名函数中的变量i都绑定到函数外部的同一个变量。

ES6 solution: let ES6 解决方案: let

ECMAScript 6 (ES6) introduces new let and const keywords that are scoped differently than var -based variables. ECMAScript 6 (ES6) 引入了新的letconst关键字,它们的作用域不同于基于var的变量。 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.例如,在具有let索引的循环中,循环中的每次迭代都会有一个新变量i具有循环范围,因此您的代码将按预期工作。 There are many resources, but I'd recommend 2ality's block-scoping post as a great source of information.有很多资源,但我推荐2ality 的块范围帖子作为重要的信息来源。

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

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 ).但请注意,IE9-IE11 和 Edge 14 之前的 Edge 支持let上述错误(它们不会每次都创建一个新的i ,所以上面的所有函数都会像我们使用var一样记录 3)。 Edge 14 finally gets it right. Edge 14 终于做对了。


ES5.1 solution: forEach ES5.1 解决方案:forEach

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

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

The idea is that each invocation of the callback function used with the .forEach loop will be its own closure.这个想法是,与.forEach循环一起使用的回调函数的每次调用都将是它自己的闭包。 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.如果在异步回调中使用它,它不会与在迭代的其他步骤中建立的任何其他回调发生冲突。

If you happen to be working in jQuery, the $.each() function gives you a similar capability.如果您碰巧使用 jQuery, $.each()函数可以为您提供类似的功能。


Classic solution: Closures经典解决方案:闭包

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

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 中没有块作用域——只有函数作用域——通过将函数创建包装在一个新函数中,您可以确保“i”的值保持您的预期。

Try:尝试:

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

Edit (2014):编辑(2014):

Personally I think @Aust's more recent answer about using .bind is the best way to do this kind of thing now.我个人认为@Aust最近关于使用.bind的回答是现在做这种事情的最好方法。 There's also lo-dash/underscore's _.partial when you don't need or want to mess with bind 's thisArg .当您不需要或不想弄乱bindthisArg _.partial

Another way that hasn't been mentioned yet is the use of Function.prototype.bind另一种尚未提及的方式是使用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](); }

UPDATE更新

As pointed out by @squint and @mekdev, you get better performance by creating the function outside the loop first and then binding the results within the loop.正如@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](); }

Using an Immediately-Invoked Function Expression , the simplest and most readable way to enclose an index variable:使用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); }

This sends the iterator i into the anonymous function of which we define as index .这会将迭代器i发送到我们定义为index的匿名函数中。 This creates a closure, where the variable i gets saved for later use in any asynchronous functionality within the IIFE.这将创建一个闭包,其中变量i被保存以供以后在 IIFE 中的任何异步功能中使用。

Bit late to the party, but I was exploring this issue today and noticed that many of the answers don't completely address how Javascript treats scopes, which is essentially what this boils down to.聚会有点晚了,但我今天正在探索这个问题,并注意到许多答案并没有完全解决 Javascript 如何处理作用域,这本质上就是归结为什么。

So as many others mentioned, the problem is that the inner function is referencing the same i variable.正如许多其他人提到的那样,问题在于内部函数引用了相同的i变量。 So why don't we just create a new local variable each iteration, and have the inner function reference that instead?那么为什么我们不只是在每次迭代中创建一个新的局部变量,而是让内部函数引用它呢?

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

Just like before, where each inner function outputted the last value assigned to i , now each inner function just outputs the last value assigned to ilocal .就像以前一样,每个内部函数输出分配给i的最后一个值,现在每个内部函数只输出分配给ilocal的最后一个值。 But shouldn't each iteration have it's own ilocal ?但是每个迭代不应该有它自己的ilocal吗?

Turns out, that's the issue.原来,这就是问题所在。 Each iteration is sharing the same scope, so every iteration after the first is just overwriting ilocal .每次迭代都共享相同的范围,因此第一次迭代之后的每次迭代都只是覆盖ilocal From MDN :来自MDN

Important: JavaScript does not have block scope.重要提示:JavaScript 没有块作用域。 Variables introduced with a block are scoped to the containing function or script, and the effects of setting them persist beyond the block itself.与块一起引入的变量的作用域是包含函数或脚本,设置它们的效果会持续到块本身之外。 In other words, block statements do not introduce a scope.换句话说,块语句不引入范围。 Although "standalone" blocks are valid syntax, you do not want to use standalone blocks in JavaScript, because they don't do what you think they do, if you think they do anything like such blocks in C or Java.尽管“独立”块是有效的语法,但您不想在 JavaScript 中使用独立块,因为如果您认为它们在 C 或 Java 中执行类似块的操作,它们不会按照您的想法执行。

Reiterated for emphasis:再次强调:

JavaScript does not have block scope. JavaScript 没有块作用域。 Variables introduced with a block are scoped to the containing function or script用块引入的变量的作用域是包含函数或脚本

We can see this by checking ilocal before we declare it in each iteration:我们可以通过在每次迭代中声明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; }

This is exactly why this bug is so tricky.这正是这个错误如此棘手的原因。 Even though you are redeclaring a variable, Javascript won't throw an error, and JSLint won't even throw a warning.即使您正在重新声明一个变量,Javascript 也不会抛出错误,而 JSLint 甚至不会抛出警告。 This is also why the best way to solve this is to take advantage of closures, which is essentially the idea that in Javascript, inner functions have access to outer variables because inner scopes "enclose" outer scopes.这也是为什么解决这个问题的最佳方法是利用闭包的原因,这本质上是在 Javascript 中,内部函数可以访问外部变量的想法,因为内部作用域“包围”了外部作用域。

闭包

This also means that inner functions "hold onto" outer variables and keep them alive, even if the outer function returns.这也意味着即使外部函数返回,内部函数也会“保留”外部变量并使它们保持活动状态。 To utilize this, we create and call a wrapper function purely to make a new scope, declare ilocal in the new scope, and return an inner function that uses ilocal (more explanation below):为了利用这一点,我们创建并调用一个包装函数纯粹是为了创建一个新作用域,在新作用域中声明ilocal ,并返回一个使用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] = (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](); }

Creating the inner function inside a wrapper function gives the inner function a private environment that only it can access, a "closure".在包装函数中创建内部函数为内部函数提供了一个只有它可以访问的私有环境,即“闭包”。 Thus, every time we call the wrapper function we create a new inner function with it's own separate environment, ensuring that the ilocal variables don't collide and overwrite each other.因此,每次我们调用包装函数时,我们都会创建一个新的内部函数,它有自己独立的环境,确保ilocal变量不会相互冲突和覆盖。 A few minor optimizations gives the final answer that many other SO users gave:一些小的优化给出了许多其他 SO 用户给出的最终答案:

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

Update更新

With ES6 now mainstream, we can now use the new let keyword to create block-scoped variables:随着 ES6 现在成为主流,我们现在可以使用新的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](); }

Look how easy it is now!看看现在多么容易! For more information see this answer , which my info is based off of.有关更多信息,请参阅此答案,我的信息基于此答案。

With ES6 now widely supported, the best answer to this question has changed.随着 ES6 现在得到广泛支持,这个问题的最佳答案已经改变。 ES6 provides the let and const keywords for this exact circumstance. ES6 为这种情况提供了letconst关键字。 Instead of messing around with closures, we can just use let to set a loop scope variable like this:我们可以使用let设置循环范围变量,而不是搞乱闭包,如下所示:

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

val will then point to an object that is specific to that particular turn of the loop, and will return the correct value without the additional closure notation.然后val将指向一个特定于循环的特定轮次的对象,并将返回正确的值而无需额外的闭包符号。 This obviously significantly simplifies this problem.这显然大大简化了这个问题。

const is similar to let with the additional restriction that the variable name can't be rebound to a new reference after initial assignment. constlet类似,但有一个额外的限制,即变量名在初始赋值后不能重新绑定到新的引用。

Browser support is now here for those targeting the latest versions of browsers.浏览器支持现在针对那些针对最新版本浏览器的用户。 const / let are currently supported in the latest Firefox, Safari, Edge and Chrome.最新的 Firefox、Safari、Edge 和 Chrome 目前支持const / let It also is supported in Node, and you can use it anywhere by taking advantage of build tools like Babel. Node 也支持它,您可以利用 Babel 等构建工具在任何地方使用它。 You can see a working example here: http://jsfiddle.net/ben336/rbU4t/2/您可以在这里看到一个工作示例:http: //jsfiddle.net/ben336/rbU4t/2/

Docs here:这里的文档:

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 ).但请注意,IE9-IE11 和 Edge 14 之前的 Edge 支持let上述错误(它们不会每次都创建一个新的i ,所以上面的所有函数都会像我们使用var一样记录 3)。 Edge 14 finally gets it right. Edge 14 终于做对了。

Another way of saying it is that the i in your function is bound at the time of executing the function, not the time of creating the function.另一种说法是函数中的i在执行函数时绑定,而不是在创建函数时绑定。

When you create the closure, i is a reference to the variable defined in the outside scope, not a copy of it as it was when you created the closure.创建闭包时, i是对外部范围中定义的变量的引用,而不是创建闭包时的副本。 It will be evaluated at the time of execution.它将在执行时进行评估。

Most of the other answers provide ways to work around by creating another variable that won't change the value for you.大多数其他答案提供了通过创建另一个不会为您更改值的变量来解决问题的方法。

Just thought I'd add an explanation for clarity.只是想我会添加一个解释清楚。 For a solution, personally, I'd go with Harto's since it is the most self-explanatory way of doing it from the answers here.对于解决方案,就个人而言,我会选择 Harto's,因为这是从这里的答案中最不言自明的方式。 Any of the code posted will work, but I'd opt for a closure factory over having to write a pile of comments to explain why I'm declaring a new variable(Freddy and 1800's) or have weird embedded closure syntax(apphacker).发布的任何代码都可以工作,但我会选择闭包工厂,而不是不得不写一堆评论来解释为什么我要声明一个新变量(Freddy 和 1800 的)或有奇怪的嵌入式闭包语法(apphacker)。

What you need to understand is the scope of the variables in javascript is based on the function.您需要了解的是javascript中变量的范围是基于函数的。 This is an important difference than say c# where you have block scope, and just copying the variable to one inside the for will work.这与说 c# 有块范围的重要区别,只需将变量复制到 for 中的一个即可。

Wrapping it in a function that evaluates returning the function like apphacker's answer will do the trick, as the variable now has the function scope.将它包装在一个评估返回函数的函数中,就像 apphacker 的答案一样,因为变量现在具有函数范围。

There is also a let keyword instead of var, that would allow using the block scope rule.还有一个 let 关键字代替 var,这将允许使用块范围规则。 In that case defining a variable inside the for would do the trick.在这种情况下,在 for 中定义一个变量就可以了。 That said, the let keyword isn't a practical solution because of compatibility.也就是说,由于兼容性,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](); }

Here's another variation on the technique, similar to Bjorn's (apphacker), which lets you assign the variable value inside the function rather than passing it as a parameter, which might be clearer sometimes:这是该技术的另一个变体,类似于 Bjorn 的 (apphacker),它允许您在函数内部分配变量值,而不是将其作为参数传递,有时可能更清楚:

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

Note that whatever technique you use, the index variable becomes a sort of static variable, bound to the returned copy of the inner function.请注意,无论您使用什么技术, index变量都会变成一种静态变量,绑定到内部函数的返回副本。 Ie, changes to its value are preserved between calls.即,在调用之间保留对其值的更改。 It can be very handy.它可以非常方便。

This describes the common mistake with using closures in JavaScript.这描述了在 JavaScript 中使用闭包的常见错误。

A function defines a new environment一个函数定义了一个新环境

Consider:考虑:

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

For each time makeCounter is invoked, {counter: 0} results in a new object being created.每次调用makeCounter时, {counter: 0}都会创建一个新对象。 Also, a new copy of obj is created as well to reference the new object.此外,还会创建obj的新副本以引用新对象。 Thus, counter1 and counter2 are independent of each other.因此, counter1counter2是相互独立的。

Closures in loops循环中的闭包

Using a closure in a loop is tricky.在循环中使用闭包很棘手。

Consider:考虑:

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

Notice that counters[0] and counters[1] are not independent.请注意, counters[0]counters[1]不是独立的。 In fact, they operate on the same obj !实际上,它们对同一个obj进行操作!

This is because there is only one copy of obj shared across all iterations of the loop, perhaps for performance reasons.这是因为只有一个obj副本在循环的所有迭代中共享,可能是出于性能原因。 Even though {counter: 0} creates a new object in each iteration, the same copy of obj will just get updated with a reference to the newest object.即使{counter: 0}在每次迭代中创建一个新对象, obj的相同副本也只会使用对最新对象的引用进行更新。

Solution is to use another helper function:解决方案是使用另一个辅助函数:

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

This works because local variables in the function scope directly, as well as function argument variables, are allocated new copies upon entry.这是因为函数作用域中的局部变量以及函数参数变量在进入时被分配了新的副本。

The most simple solution would be,最简单的解决方案是,

Instead of using:而不是使用:

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

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

which alerts "2", for 3 times.警报“2”,持续 3 次。 This is because anonymous functions created in for loop, shares same closure, and in that closure, the value of i is the same.这是因为在 for 循环中创建的匿名函数共享相同的闭包,并且在该闭包中, i的值是相同的。 Use this to prevent shared closure:使用它来防止共享关闭:

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

The idea behind this is, encapsulating the entire body of the for loop with an IIFE (Immediately-Invoked Function Expression) and passing new_i as a parameter and capturing it as i .这背后的想法是,用IIFE (立即调用函数表达式)封装 for 循环的整个主体,并将new_i作为参数传递并将其捕获为i Since the anonymous function is executed immediately, the i value is different for each function defined inside the anonymous function.由于匿名函数是立即执行的,因此匿名函数内部定义的每个函数的i值都是不同的。

This solution seems to fit any such problem since it will require minimal changes to the original code suffering from this issue.该解决方案似乎适合任何此类问题,因为它需要对遭受此问题的原始代码进行最小的更改。 In fact, this is by design, it should not be an issue at all!事实上,这是设计使然,根本不应该成为问题!

Here's a simple solution that uses forEach (works back to IE9):这是一个使用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 }

Prints:印刷:

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

try this shorter one试试这个更短的

  • no array没有数组

  • no extra for loop没有额外的 for 循环


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

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

http://jsfiddle.net/7P6EN/ http://jsfiddle.net/7P6EN/

The main issue with the code shown by the OP is that i is never read until the second loop. OP 显示的代码的主要问题是i直到第二个循环才被读取。 To demonstrate, imagine seeing an error inside of the code为了演示,想象一下在代码中看到一个错误

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

The error actually does not occur until funcs[someIndex] is executed () .在执行funcs[someIndex] ()之前,实际上不会发生错误。 Using this same logic, it should be apparent that the value of i is also not collected until this point either.使用相同的逻辑,很明显i的值也直到此时才被收集。 Once the original loop finishes, i++ brings i to the value of 3 which results in the condition i < 3 failing and the loop ending.一旦原始循环完成, i++i的值设为3 ,这导致条件i < 3失败并且循环结束。 At this point, i is 3 and so when funcs[someIndex]() is used, and i is evaluated, it is 3 - every time.此时, i3 ,因此当使用funcs[someIndex]()并评估i时,每次都是 3。

To get past this, you must evaluate i as it is encountered.要克服这一点,您必须在遇到i时对其进行评估。 Note that this has already happened in the form of funcs[i] (where there are 3 unique indexes).请注意,这已经以funcs[i]的形式发生(其中有 3 个唯一索引)。 There are several ways to capture this value.有几种方法可以获取此值。 One is to pass it in as a parameter to a function which is shown in several ways already here.一种是将其作为参数传递给函数,该函数已经在此处以多种方式显示。

Another option is to construct a function object which will be able to close over the variable.另一种选择是构造一个能够关闭变量的函数对象。 That can be accomplished thusly可以这样实现

jsFiddle Demo

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

JavaScript functions "close over" the scope they have access to upon declaration, and retain access to that scope even as variables in that scope change. 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]() }

Each function in the array above closes over the global scope (global, simply because that happens to be the scope they're declared in).上面数组中的每个函数都关闭了全局范围(全局,仅仅是因为那恰好是它们声明的范围)。

Later those functions are invoked logging the most current value of i in the global scope.稍后调用这些函数,记录全局范围内i的最新值。 That's the magic, and frustration, of closure.这就是关闭的魔力和挫败感。

"JavaScript Functions close over the scope they are declared in, and retain access to that scope even as variable values inside of that scope change." “JavaScript 函数在它们声明的范围内关闭,并保留对该范围的访问,即使该范围内的变量值发生变化。”

Using let instead of var solves this by creating a new scope each time the for loop runs, creating a separated scope for each function to close over.使用let而不是var通过在每次for循环运行时创建一个新范围来解决这个问题,为每个函数创建一个单独的范围以关闭。 Various other techniques do the same thing with extra functions.各种其他技术通过额外的功能做同样的事情。

 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 makes variables block scoped. Blocks are denoted by curly braces, but in the case of the for loop the initialization variable, i in our case, is considered to be declared in the braces.) let使变量成为块范围。块用花括号i ,但在 for 循环的情况下,初始化变量,在我们的例子中,被认为是在花括号中声明的。)

After reading through various solutions, I'd like to add that the reason those solutions work is to rely on the concept of scope chain .在阅读了各种解决方案之后,我想补充一点,这些解决方案起作用的原因是依赖于范围链的概念。 It's the way JavaScript resolve a variable during execution.这是 JavaScript 在执行期间解析变量的方式。

  • Each function definition forms a scope consisting of all the local variables declared by var and its arguments .每个函数定义形成一个范围,由var声明的所有局部变量及其arguments组成。
  • If we have inner function defined inside another (outer) function, this forms a chain, and will be used during execution如果我们在另一个(外部)函数中定义了内部函数,这将形成一个链,并将在执行期间使用
  • When a function gets executed, the runtime evaluates variables by searching the scope chain .当一个函数被执行时,运行时通过搜索作用域链来评估变量。 If a variable can be found in a certain point of the chain it will stop searching and use it, otherwise it continues until the global scope reached which belongs to window .如果可以在链的某个点找到变量,它将停止搜索并使用它,否则它会继续直到达到属于window的全局范围。

In the initial code:在初始代码中:

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

When funcs gets executed, the scope chain will be function inner -> global .funcs被执行时,作用域链将是function inner -> global Since the variable i cannot be found in function inner (neither declared using var nor passed as arguments), it continues to search, until the value of i is eventually found in the global scope which is window.i .由于在function inner中找不到变量i (既没有使用var声明也没有作为参数传递),它继续搜索,直到最终在全局范围内找到i的值,即window.i

By wrapping it in an outer function either explicitly define a helper function like harto did or use an anonymous function like Bjorn did:通过将它包装在一个外部函数中,或者显式定义一个像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

When funcs gets executed, now the scope chain will be function inner -> function outer .funcs被执行时,现在作用域链将是function inner -> function outer This time i can be found in the outer function's scope which is executed 3 times in the for loop, each time has value i bound correctly.这次i可以在外部函数的作用域中找到,该作用域在 for 循环中执行了 3 次,每次都有i正确绑定的值。 It won't use the value of window.i when inner executed.内部执行时不会使用window.i的值。

More detail can be found here更多细节可以在这里找到
It includes the common mistake in creating closure in the loop as what we have here, as well as why we need closure and the performance consideration.它包括在循环中创建闭包的常见错误,以及我们需要闭包的原因和性能考虑。

With new features of ES6 block level scoping is managed:使用 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
}

The code in OP's question is replaced with let instead of var . OP 问题中的代码被替换为let而不是var

I'm surprised no one yet has suggested using the forEach function to better avoid (re)using local variables.我很惊讶没有人建议使用forEach函数来更好地避免(重新)使用局部变量。 In fact, I'm not using for(var i ...) at all anymore for this reason.事实上,出于这个原因,我根本不再使用for(var i ...)了。

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

// edited to use forEach instead of map. // 编辑为使用forEach而不是 map。

This question really shows the history of JavaScript!这个问题真的展示了 JavaScript 的历史! Now we can avoid block scoping with arrow functions and handle loops directly from DOM nodes using Object methods.现在我们可以避免使用箭头函数的块作用域,并使用 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>

We will check , what actually happens when you declare var and let one by one.我们将检查,当您声明varlet时实际发生了什么。

Case1 : using var案例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>

Now open your chrome console window by pressing F12 and refresh the page.现在按F12打开您的chrome 控制台窗口并刷新页面。 Expend every 3 functions inside the array.You will see an property called [[Scopes]] .Expand that one.扩展数组中的每 3 个函数。您将看到一个名为[[Scopes]]的属性。展开该属性。 You will see one array object called "Global" ,expand that one.您将看到一个名为"Global"的数组对象,展开该对象。 You will find a property 'i' declared into the object which having value 3.您会发现在对象中声明了一个属性'i' ,其值为 3。

在此处输入图像描述

在此处输入图像描述

Conclusion:结论:

  1. When you declare a variable using 'var' outside a function ,it becomes global variable(you can check by typing i or window.i in console window.It will return 3).当你在函数外使用'var'声明一个变量时,它变成了全局变量(你可以通过在控制台窗口中输入iwindow.i来检查。它将返回 3)。
  2. The anonymous function you declared will not call and check the value inside the function unless you invoke the functions.您声明的匿名函数不会调用并检查函数内部的值,除非您调用这些函数。
  3. When you invoke the function , console.log("My value: " + i) takes the value from its Global object and display the result.当您调用该函数时, console.log("My value: " + i)从其Global对象中获取值并显示结果。

CASE2 : using let案例2:使用让

Now replace the 'var' with 'let'现在用'let'替换'var' '

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

Do the same thing, Go to the scopes .做同样的事情,转到范围。 Now you will see two objects "Block" and "Global" .现在您将看到两个对象"Block""Global" Now expand Block object , you will see 'i' is defined there , and the strange thing is that , for every functions , the value if i is different (0 , 1, 2).现在展开Block对象,你会看到 'i' 被定义在那里,奇怪的是,对于每个函数,如果i不同(0、1、2)的值。

在此处输入图像描述

Conclusion:结论:

When you declare variable using 'let' even outside the function but inside the loop , this variable will not be a Global variable , it will become a Block level variable which is only available for the same function only.That is the reason , we are getting value of i different for each function when we invoke the functions.当你在函数外但在循环内使用'let'声明变量时,这个变量将不是全局变量,它将成为一个Block级变量,只能用于同一个函数。这就是原因,我们是当我们调用函数时,为每个函数获取不同的i值。

For more detail about how closer works , please go through the awesome video tutorial https://youtu.be/71AtaJpJHw0有关更紧密的工作原理的更多详细信息,请观看精彩的视频教程https://youtu.be/71AtaJpJHw0

The reason your original example did not work is that all the closures you created in the loop referenced the same frame.您的原始示例不起作用的原因是您在循环中创建的所有闭包都引用了同一帧。 In effect, having 3 methods on one object with only a single i variable.实际上,在一个对象上有 3 个方法,只有一个i变量。 They all printed out the same value.他们都打印出相同的值。

First of all, understand what's wrong with this code:首先,了解这段代码有什么问题:

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
}

Here when the funcs[] array is being initialized, i is being incremented, the funcs array is initialized and the size of func array becomes 3, so i = 3, .这里初始化funcs[]数组时, i递增, funcs数组初始化, func数组的大小变为3,所以i = 3, Now when the funcs[j]() is called, it is again using the variable i , which has already been incremented to 3.现在,当调用funcs[j]()时,它再次使用变量i ,该变量已递增到 3。

Now to solve this, we have many options.现在要解决这个问题,我们有很多选择。 Below are two of them:以下是其中的两个:

  1. We can initialize i with let or initialize a new variable index with let and make it equal to i .我们可以用let初始化i或用let初始化一个新的变量index并使其等于i So when the call is being made, index will be used and its scope will end after initialization.因此,在进行调用时,将使用index ,并且其范围将在初始化后结束。 And for calling, index will be initialized again:并且对于调用, index将再次被初始化:

     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. Other Option can be to introduce a tempFunc which returns the actual function:其他选项可以是引入一个返回实际函数的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](); }

Use closure structure, this would reduce your extra for loop.使用闭包结构,这将减少你额外的 for 循环。 You can do it in a single for loop:您可以在单个 for 循环中执行此操作:

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

Till ES5, This problem can only be solved using closure .直到 ES5,这个问题只能使用闭包来解决。

But now in ES6, we have block level scope variables.但是现在在 ES6 中,我们有了块级作用域变量。 Changing var to let in first for loop will solve the problem.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 }

If you're having this sort of problem with a while loop, rather than a for loop, for example:如果您在使用while循环而不是for循环时遇到此类问题,例如:

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

The technique to close over the current value is a bit different.关闭当前值的技术有点不同。 Declare a block-scoped variable with const inside the while block, and assign the current i to it.while块内用const声明一个块范围的变量,并将当前i分配给它。 Then, wherever the variable is being used asynchronously, replace i with the new block-scoped variable:然后,在异步使用变量的任何地方,将i替换为新的块范围变量:

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

For older browsers that don't support block-scoped variables, you can use an IIFE called with i :对于不支持块范围变量的旧浏览器,您可以使用带有i调用的 IIFE:

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

If the asynchronous action to be invoked happens to be setTimeout like the above, you can also call setTimeout with a third parameter to indicate the argument to call the passed function with:如果要调用的异步操作恰好是上面的setTimeout ,您还可以使用第三个参数调用setTimeout ,以指示调用传递函数的参数:

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

You could use a declarative module for lists of data such as query-js (*).您可以将声明性模块用于数据列表,例如query-js (*)。 In these situations I personally find a declarative approach less surprising在这些情况下,我个人发现声明式方法不那么令人惊讶

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

You could then use your second loop and get the expected result or you could do然后,您可以使用第二个循环并获得预期的结果,或者您可以这样做

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

(*) I'm the author of query-js and therefor biased towards using it, so don't take my words as a recommendation for said library only for the declarative approach :) (*) 我是 query-js 的作者,因此偏向于使用它,所以不要将我的话作为对所述库的推荐,仅用于声明性方法:)

I prefer to use forEach function, which has its own closure with creating a pseudo range:我更喜欢使用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
}

That looks uglier than ranges in other languages, but IMHO less monstrous than other solutions.这看起来比其他语言的范围更丑,但恕我直言,没有其他解决方案那么可怕。

And yet another solution: instead of creating another loop, just bind the this to the return function.还有另一个解决方案:无需创建另一个循环,只需将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 }

By binding this , solves the problem as well.通过绑定this ,也可以解决问题。

Your code doesn't work, because what it does is:您的代码不起作用,因为它的作用是:

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?

Now the question is, what is the value of variable i when the function is called?现在的问题是,调用函数时变量i的值是多少? Because the first loop is created with the condition of i < 3 , it stops immediately when the condition is false, so it is i = 3 .因为第一个循环是在i < 3条件下创建的,所以当条件为假时它会立即停止,所以它是i = 3

You need to understand that, in time when your functions are created, none of their code is executed, it is only saved for later.你需要明白,当你的函数被创建时,它们的代码都不会被执行,它只是被保存以备后用。 And so when they are called later, the interpreter executes them and asks: "What is the current value of i ?"因此,当稍后调用它们时,解释器会执行它们并询问:“ i的当前值是多少?”

So, your goal is to first save the value of i to function and only after that save the function to funcs .因此,您的目标是首先将i的值保存到 function ,然后才将函数保存到funcs This could be done for example this way:例如,这可以通过以下方式完成:

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
}

This way, each function will have it's own variable x and we set this x to the value of i in each iteration.这样,每个函数都有自己的变量x ,我们在每次迭代中将这个x设置为i的值。

This is only one of the multiple ways to solve this problem.这只是解决此问题的多种方法之一。

Many solutions seem correct but they don't mention it's called Currying which is a functional programming design pattern for situations like here.许多解决方案似乎是正确的,但他们没有提到它被称为Currying ,这是一种适用于此类情况的函数式编程设计模式。 3-10 times faster than bind depending on the browser.比绑定快 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);
  }
}

See the performance gain in different browsers .查看不同浏览器的性能提升

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
}

Use let(blocked-scope) instead of var. 使用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](); } 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM