Javascript:在 For 循环中创建函数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19696015/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Javascript: Creating Functions in a For Loop
提问by Ardeol
Recently, I found myself needing to create an array of functions. The functions use values from an XML document, and I am running through the appropriate nodes with a for loop. However, upon doing this, I found that only the last node of the XML sheet (corresponding to the last run of the for loop) was ever used by all of the functions in the array.
最近,我发现自己需要创建一个函数数组。这些函数使用来自 XML 文档的值,我正在使用 for 循环遍历适当的节点。但是,在执行此操作时,我发现数组中的所有函数都只使用了 XML 表的最后一个节点(对应于 for 循环的最后一次运行)。
The following is an example that showcases this:
以下是展示这一点的示例:
var numArr = [];
var funArr = [];
for(var i = 0; i < 10; ++i){
numArr[numArr.length] = i;
funArr[funArr.length] = function(){ return i; };
}
window.alert("Num: " + numArr[5] + "\nFun: " + funArr[5]());
The output is Num: 5 and Fun: 10.
输出为 Num: 5 和 Fun: 10。
Upon research, I found a a segment of code that works, but I am struggling to understand precisely why it works. I reproduced it here using my example:
经过研究,我发现了一段有效的代码,但我正在努力理解它为什么有效。我在这里用我的例子复制了它:
var funArr2 = [];
for(var i = 0; i < 10; ++i)
funArr2[funArr2.length] = (function(i){ return function(){ return i;}})(i);
window.alert("Fun 2: " + funArr2[5]());
I know it has to do with scoping, but at first glance it does not seem like it would perform any differently from my naive approach. I am somewhat of a beginner in Javascript, so if I may ask, why is it that using this function-returning-a-function technique bypasses the scoping issue? Also, why is the (i) included on the end?
我知道这与范围界定有关,但乍一看,它的表现似乎与我的幼稚方法没有任何不同。我是 Javascript 的初学者,所以如果我问,为什么使用这种函数返回函数技术绕过了作用域问题?另外,为什么 (i) 包含在末尾?
Thank you very much in advance.
非常感谢您提前。
采纳答案by Ted Hopp
The second method is a little clearer if you use a parameter name that does not mask the loop variable name:
如果使用不屏蔽循环变量名称的参数名称,则第二种方法会更清晰一些:
funArr[funArr.length] = (function(val) { return function(){ return val; }})(i);
The problem with your current code is that each function is a closureand they all reference the same variable i
. When each function is run, it returns the value of i
at the time the function is run (which will be one more than the limit value for the loop).
您当前代码的问题在于每个函数都是一个闭包,并且它们都引用相同的变量i
。当每个函数运行时,它返回函数运行时的值i
(将比循环的限制值多一)。
A clearer way would be to write a separate function that returns the closure that you want:
更清晰的方法是编写一个单独的函数来返回您想要的闭包:
var numArr = [];
var funArr = [];
for(var i = 0; i < 10; ++i){
numArr[numArr.length] = i;
funArr[funArr.length] = getFun(i);
}
function getFun(val) {
return function() { return val; };
}
Note that this is doing basically the same thing as the first line of code in my answer: calling a function that returns a function and passing the value of i
as a parameter. It's main advantage is clarity.
请注意,这与我的答案中的第一行代码执行的操作基本相同:调用返回函数的函数并将 的值i
作为参数传递。它的主要优点是清晰度。
EDIT:Now that EcmaScript 6 is supported almost everywhere (sorry, IE users), you can get by with a simpler approach—use the let
keyword instead of var
for the loop variable:
编辑:现在几乎所有地方都支持 EcmaScript 6(对不起,IE 用户),您可以使用更简单的方法——使用let
关键字而不是var
for 循环变量:
var numArr = [];
var funArr = [];
for(let i = 0; i < 10; ++i){
numArr[numArr.length] = i;
funArr[funArr.length] = function(){ return i; };
}
With that little change, each funArr
element is a closure bound do a differenti
object on each loop iteration. For more info on let
, see this Mozilla Hacks post from 2015. (If you're targeting environments that don't support let
, stick with what I wrote earlier, or run this last through a transpiler before using.
有了这个小小的改变,每个funArr
元素都是一个闭包绑定,在每次循环迭代中做一个不同的i
对象。有关更多信息let
,请参阅2015 年的 Mozilla Hacks 帖子。(如果您的目标环境不支持let
,请坚持我之前写的内容,或者在使用前最后通过转译器运行它。
回答by Chris Hayes
Let's investigate what the code does a little closer and assign imaginary function names:
让我们更仔细地研究一下代码做了什么,并分配了虚构的函数名称:
(function outer(i) {
return function inner() {
return i;
}
})(i);
Here, outer
receives an argument i
. JavaScript employs function scoping, meaning that each variable exists only within the function it is defined in. i
here is defined in outer
, and therefore exists in outer
(and any scopes enclosed within).
在这里,outer
接收一个参数i
。JavaScript 使用函数作用域,这意味着每个变量只存在于定义它的函数中。i
这里定义在 中outer
,因此存在于outer
(以及包含在其中的任何作用域)。
inner
contains a reference to the variable i
. (Note that it does notredefine i
as a parameter or with the var
keyword!) JavaScript's scoping rules state that such a reference should be tied to the first enclosing scope, which here is the scope of outer
. Therefore, i
within inner
refers to the same i
that was within outer
.
inner
包含对变量的引用i
。(请注意,它并没有重新定义i
的参数或用var
关键字!)JavaScript的作用域规则的状态,这样的基准应该连接到第一封闭范围,在这里是的范围outer
。因此,i
内inner
指的是相同的i
,这是内outer
。
Finally, after defining the function outer
, we immediately call it, passing it the value i
(which is a separate variable, defined in the outermost scope). The value i
is enclosed within outer
, and its value cannot now be changed by any code within the outermost scope. Thus, when the outermost i
is incremented in the for
loop, the i
within outer
keeps the same value.
最后,在定义了 function 之后outer
,我们立即调用它,将值传递给它i
(这是一个单独的变量,定义在最外层的作用域中)。该值i
包含在 中outer
,并且其值现在不能被最外层范围内的任何代码更改。因此,当最外i
的递增for
环,所述i
内outer
保持相同的值。
Remembering that we've actually created a number of anonymous functions, each with its own scope and argument values, it is hopefully clear how it is that each of these anonymous functions retains its own value for i
.
记住我们实际上已经创建了许多匿名函数,每个函数都有自己的作用域和参数值,希望很清楚这些匿名函数中的每一个如何保留自己的值i
。
Finally, for completeness, let's examine what happened with the original code:
最后,为了完整起见,让我们检查一下原始代码发生了什么:
for(var i = 0; i < 10; ++i){
numArr[numArr.length] = i;
funArr[funArr.length] = function(){ return i; };
}
Here, we can see the anonymous function contains a reference to the outermost i
. As that value changes, it will be reflected within the anonymous function, which does not retain its own copy of the value in any form. Thus, since i == 10
in the outermost scope at the time that we go and call all of these functions we've created, each function will return the value 10
.
在这里,我们可以看到匿名函数包含对最外层 的引用i
。随着该值的变化,它将反映在匿名函数中,该函数不会以任何形式保留自己的值副本。因此,由于i == 10
在我们调用所有这些我们创建的函数时的最外层作用域中,每个函数都将返回 value 10
。
回答by linstantnoodles
I recommend picking up a book like JavaScript: The Definitive Guideto gain a deeper understanding of JavaScript in general so you can avoid common pitfalls like this.
我建议你读一本像JavaScript: The Definitive Guide这样的书来深入了解 JavaScript,这样你就可以避免像这样的常见陷阱。
This answer also provides a decent explanation on closures specifically:
这个答案还专门提供了关于闭包的体面解释:
How do JavaScript closures work?
When you invoke
当你调用
function() { return i; }
the function is actually doing a variable look-up on the parent call-object (scope), which is where i is defined. In this case, i is defined as 10, and so every single one of those functions will return 10. The reason this works
该函数实际上是在父调用对象(范围)上进行变量查找,这是定义 i 的地方。在这种情况下,i 被定义为 10,因此这些函数中的每一个都将返回 10。这是有效的原因
(function(i){ return function(){ return i;}})(i);
is that by invoking an anonymous function immediately, a new call-object is created in which the current iis defined. So when you invoke the nested function, that function refers to the call-object of the anonymous function (which defines whatever value you passed to it when it was invoked), not the scope in which i was originally defined (which is still 10).
是通过立即调用匿名函数,创建了一个新的调用对象,其中定义了当前的 i。因此,当您调用嵌套函数时,该函数指的是匿名函数的调用对象(它定义了您在调用时传递给它的任何值),而不是 i 最初定义的范围(仍然是 10) .