【问题标题】:Javascript: Creating Functions in a For LoopJavascript:在 For 循环中创建函数
【发布时间】:2013-10-30 23:53:18
【问题描述】:

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

非常感谢您。

【问题讨论】:

    标签: javascript function


    【解决方案1】:

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

    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 关键字而不是 var 进行循环变量:

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

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

    【讨论】:

    • 我是 JS 新手,非常感谢,在您的帖子之后,我学会了如何动态创建函数(在我的例子中是回调)!
    • 非常有帮助。只是用它来解决我的问题。 stackoverflow.com/questions/41254076/… 非常感谢。
    【解决方案2】:

    让我们更深入地研究一下代码的作用并分配虚构的函数名称:

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

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

    inner 包含对变量 i 的引用。 (请注意,它i 重新定义为参数或使用var 关键字!)JavaScript 的作用域规则规定,此类引用应绑定到第一个封闭作用域,这里是outer 的范围。因此,inner 中的i 指的是与outer 相同的i

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

    请记住,我们实际上创建了许多匿名函数,每个函数都有自己的范围和参数值,希望清楚这些匿名函数中的每一个如何为i 保留自己的值。

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

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

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

    【讨论】:

      【解决方案3】:

      我建议阅读像 JavaScript:权威指南 这样的书,以更深入地了解 JavaScript,从而避免常见的此类陷阱。

      这个答案还专门针对闭包提供了一个不错的解释:

      How do JavaScript closures work?

      当你调用时

      function() { return i; }
      

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

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

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

      【讨论】:

      • 非常有帮助。谢谢!
      猜你喜欢
      • 1970-01-01
      • 2012-09-11
      • 2021-09-11
      • 1970-01-01
      • 2012-07-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多