[英]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 in
和for 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
都绑定到函数外部的同一个变量。
let
let
ECMAScript 6 (ES6) introduces new let
and const
keywords that are scoped differently than var
-based variables. ECMAScript 6 (ES6) 引入了新的
let
和const
关键字,它们的作用域不同于基于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 终于做对了。
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()
函数可以为您提供类似的功能。
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
.当您不需要或不想弄乱
bind
的thisArg
_.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 为这种情况提供了
let
和const
关键字。 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. const
与let
类似,但有一个额外的限制,即变量名在初始赋值后不能重新绑定到新的引用。
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 中使用闭包的常见错误。
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.因此,
counter1
和counter2
是相互独立的。
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
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);};
}
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.此时,
i
为3
,因此当使用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
可以这样实现
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 在执行期间解析变量的方式。
var
and its arguments
.var
声明的所有局部变量及其arguments
组成。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
andlet
one by one.我们将检查,当您声明
var
和let
时实际发生了什么。
var
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:结论:
'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'
声明一个变量时,它变成了全局变量(你可以通过在控制台窗口中输入i
或window.i
来检查。它将返回 3)。console.log("My value: " + i)
takes the value from its Global
object and display the result.console.log("My value: " + i)
从其Global
对象中获取值并显示结果。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:
以下是其中的两个:
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](); }
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](); }
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.