最近,我发现自己需要创建一系列函数。这些函数使用XML文档中的值,我使用for循环运行相应的节点。但是,在执行此操作时,我发现只有XML工作表的最后一个节点(对应于for循环的最后一次运行)才被数组中的所有函数使用。
以下是展示此内容的示例:
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]());
输出为Num:5和Fun:10。
经过研究,我找到了a segment of code that works,但我很难理解为什么它有效。我使用我的例子在这里复制了它:
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]());
我知道这与范围确定有关,但乍一看似乎它与我天真的方法没有任何不同。我有点像Javascript的初学者,所以如果我可能会问,为什么使用这个函数返回函数技术会绕过作用域问题?另外,为什么(i)包括在最后?
非常感谢你。
答案 0 :(得分:33)
如果使用不掩盖循环变量名的参数名,第二种方法会更清晰:
funArr[funArr.length] = (function(val) { return function(){ return val; }})(i);
您当前代码的问题是每个函数都是closure,它们都引用相同的变量i
。运行每个函数时,它会在函数运行时返回i
的值(这将比循环的极限值多一个)。
更清楚的方法是编写一个单独的函数来返回你想要的闭包:
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; };
}
请注意,这与我的答案中的第一行代码基本相同:调用一个返回函数并将i
的值作为参数传递的函数。它的主要优点是清晰度。
编辑:现在几乎所有地方都支持EcmaScript 6(对不起,IE用户),您可以采用更简单的方法 - 使用let
关键字代替{{1}对于循环变量:
var
通过这个小小的改动,每个var numArr = [];
var funArr = [];
for(let i = 0; i < 10; ++i){
numArr[numArr.length] = i;
funArr[funArr.length] = function(){ return i; };
}
元素都是一个闭包,在每次循环迭代中做一个不同的 funArr
对象。有关i
的详细信息,请参阅this Mozilla Hacks post from 2015。 (如果您的目标是不支持let
的环境,请坚持使用我之前编写的内容,或者在使用之前通过转发器运行此内容。
答案 1 :(得分:6)
让我们研究一下代码的作用,并分配虚构的函数名称:
(function outer(i) {
return function inner() {
return i;
}
})(i);
此处,outer
收到参数i
。 JavaScript使用函数作用域,这意味着每个变量仅存在于其定义的函数中。i
此处在outer
中定义,因此存在于outer
中(以及其中的任何范围。)
inner
包含对变量i
的引用。 (请注意,不重新定义i
作为参数或使用var
关键字!)JavaScript的范围规则规定此类引用应与第一个封闭范围相关联,这是outer
的范围。因此,i
中的inner
指的是i
内的outer
。
最后,在定义函数outer
之后,我们立即调用它,向它传递值i
(这是一个单独的变量,在最外层的作用域中定义)。值i
包含在outer
中,现在最近范围内的任何代码都无法更改其值。因此,当最外面的i
在for
循环中递增时,i
中的outer
保持相同的值。
记住我们实际上已经创建了许多匿名函数,每个函数都有自己的范围和参数值,希望清楚这些匿名函数是如何为i
保留自己的值的。< / p>
最后,为了完整性,我们来看看原始代码发生了什么:
for(var i = 0; i < 10; ++i){
numArr[numArr.length] = i;
funArr[funArr.length] = function(){ return i; };
}
在这里,我们可以看到匿名函数包含对最外层i
的引用。当该值发生变化时,它将反映在匿名函数中,该函数不会以任何形式保留其自己的值副本。因此,由于我们去的最外层范围内的i == 10
调用了我们创建的所有这些函数,因此每个函数都将返回值10
。
答案 2 :(得分:5)
我建议您选择 JavaScript:The Definitive Guide 这样的书,以便更深入地了解JavaScript,这样您就可以避免像这样的常见陷阱。
这个答案还特别为闭包提供了一个不错的解释:
How do JavaScript closures work?
当您调用
时function() { return i; }
该函数实际上是在父调用对象(范围)上进行变量查找,这是我定义的地方。在这种情况下,i被定义为10,因此这些函数中的每一个都将返回10.其工作原理
(function(i){ return function(){ return i;}})(i);
是通过立即调用匿名函数,创建一个新的调用对象,其中定义了当前i 。因此,当您调用嵌套函数时,该函数引用匿名函数的调用对象(它定义了调用时传递给它的任何值),而不是最初定义i的范围(仍然是10)