Javascript:在For循环中创建函数

时间:2013-10-30 23:53:18

标签: javascript function

最近,我发现自己需要创建一系列函数。这些函数使用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)包括在最后?

非常感谢你。

3 个答案:

答案 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中,现在最近范围内的任何代码都无法更改其值。因此,当最外面的ifor循环中递增时,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)