Javascript:在 For 循环中创建函数

IT技术 javascript function
2021-02-19 17:47:47

最近,我发现自己需要创建一个函数数组。这些函数使用来自 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。

经过研究,我发现了一段有效的代码,但我正在努力理解它为什么有效。我在这里用我的例子复制了它:

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个回答

如果使用不屏蔽循环变量名称的参数名称,则第二种方法会更清晰一些:

funArr[funArr.length] = (function(val) { return function(){  return val; }})(i);

您当前代码的问题在于每个函数都是一个闭包,并且它们都引用相同的变量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关键字而不是varfor 循环变量:

var numArr = [];
var funArr = [];
for(let i = 0; i < 10; ++i){
    numArr[numArr.length] = i;
    funArr[funArr.length] = function(){  return i; };
}

有了这个小小的改变,每个funArr元素都是一个闭包绑定,在每次循环迭代中做一个不同的 i对象。有关更多信息let,请参阅2015 年的 Mozilla Hacks 帖子(如果您的目标环境不支持let,请坚持我之前写的内容,或者在使用前最后通过转译器运行它。

我是 JS 新手,所以非常感谢,在你的帖子之后我学会了如何动态创建函数(在我的例子中是回调)!
2021-04-23 17:47:47
非常有帮助。只是用它来解决我的问题。stackoverflow.com/questions/41254076/...非常感谢。
2021-04-30 17:47:47

让我们更仔细地研究一下代码做了什么,并分配了虚构的函数名称:

(function outer(i) { 
    return function inner() { 
        return i;
    }
 })(i);

在这里,outer接收一个参数iJavaScript 使用函数作用域,这意味着每个变量只存在于定义它的函数中。i这里定义在 中outer,因此存在于outer(以及包含在其中的任何作用域)。

inner包含对变量的引用i(请注意,它并没有重新定义i的参数或用var关键字!)JavaScript的作用域规则的状态,这样的基准应该连接到第一封闭范围,在这里是的范围outer因此,iinner指的是相同的i,这是内outer

最后,在定义了 function 之后outer,我们立即调用它,将值传递给它i(这是一个单独的变量,定义在最外层的作用域中)。该值i包含在 中outer,并且其值现在不能被最外层范围内的任何代码更改。因此,当最外i的递增for环,所述iouter保持相同的值。

记住我们实际上已经创建了许多匿名函数,每个函数都有自己的作用域和参数值,希望很清楚这些匿名函数中的每一个如何保留自己的值i

最后,为了完整起见,让我们检查一下原始代码发生了什么:

for(var i = 0; i < 10; ++i){
    numArr[numArr.length] = i;
    funArr[funArr.length] = function(){  return i; };
}

在这里,我们可以看到匿名函数包含对最外层 的引用i随着该值的变化,它将反映在匿名函数中,该函数不会以任何形式保留自己的值副本。因此,由于i == 10在我们调用所有这些我们创建的函数时的最外层作用域中,每个函数都将返回 value 10

我建议你读一本像JavaScript: The Definitive Guide这样的书来深入了解 JavaScript,这样你就可以避免像这样的常见陷阱。

这个答案还专门提供了关于闭包的体面解释:

JavaScript 闭包是如何工作的?

当你调用

function() { return i; }

该函数实际上是在父调用对象(范围)上进行变量查找,这是定义 i 的地方。在这种情况下,i 被定义为 10,因此这些函数中的每一个都将返回 10。这是有效的原因

(function(i){ return function(){ return i;}})(i);

是通过立即调用匿名函数,创建了一个新的调用对象,其中定义当前的 i因此,当您调用嵌套函数时,该函数指的是匿名函数的调用对象(它定义了您在调用时传递给它的任何值),而不是 i 最初定义的范围(仍然是 10) .

很有帮助。谢谢!
2021-05-06 17:47:47