【问题标题】:jquery callback function only working on last loopjquery 回调函数仅在最后一个循环中起作用
【发布时间】:2009-10-13 18:35:54
【问题描述】:
for (var i = 0; i < barValues.length; i++) {


    actualBarHeight = Math.floor((barValues[i] / chartMaxY) * barchartHeight);

    var barChartID = "#barChart" + (i + 1)
    $(barChartID + " .value span").css('background-color', 'transparent');
    $(barChartID + " img").animate({
        height: actualBarHeight
    }, 500, function () {
        $(barChartID + " .value span").css('background-color', 'white');
    }
    );

    $(barChartID + " .value span").html("$" + Math.floor(barValues[i]));
    $(barChartID + " .value").css("bottom", actualBarHeight + "px");
    $(barChartID + " .ylabel").html(chartMaxY);

}

上面的 jQuery 代码在一个 for 循环中。循环的每次迭代都执行以下操作:

  • 设置跨度的背景
  • 动画对象
  • 完成后,重置 span 的背景

我正在使用回调函数来重置背景,以便它在这样做之前完成动画。但是,它最终只会影响 for 循环中引用的最后一个跨度。

如果我将那段代码移到回调之外,那么它会影响 for 循环的每次迭代中的每个跨度,(但在这种情况下不会等待动画)

我猜这个问题与在 animate 函数内部的函数内部构建选择器有关。我的标记中有一些不好的语法吗?

编辑(根据 Russ 的建议,我现在在上面的示例中包含完整的循环)

【问题讨论】:

  • @DA - 你能发布完整的代码,包括包络循环吗?听起来循环有问题(可能缺少闭包),所以最好看看循环看看它是如何实现的
  • Russ:好建议。我已经更新了示例。
  • 仅供参考,它是完全循环的,因为它在每组元素的循环中执行所有其他操作。奇怪的似乎是试图修改 .animate 的回调函数中的任何内容

标签: jquery callback


【解决方案1】:

这是在将闭包与循环组合时遇到的常见问题。 JavaScript 是一种后期绑定语言,循环不会引入新的作用域。所以:

for (var i= 0; i<5; i++) {
    $('#thing'+i).click(function() {
        alert(i);
    });
}

这段代码中只有一个i 变量。它从0 开始,一旦分配循环完成,就留在5#thing0 元素上的点击事件只会在循环完成执行后 被触发,此时i 的值将是5。您不会得到您可能期望的定义时间值0

这不仅适用于循环变量本身,也适用于您在每次循环中重新分配的任何其他变量。因此,在您的示例中,动画回调函数中 barChartID 的值将始终是与循环中最后一个元素关联的 id。

通常的解决方法是在定义时使用一个确实引入新范围的结构,即另一个函数,来获取循环变量值的副本:

$(barChartID + " img").animate({height: actualBarHeight}, 500, function(barChartID) {
    return function() {
        $(barChartID + " .value span").css('background-color','white');
    };
}(barChartID));

More on looped closures.

【讨论】:

  • bobince:哇!感谢您的出色解释!您的示例代码完美运行。我不完全遵循语法(还),但我现在明白发生了什么。为了跟踪逻辑,看起来我的回调函数本身就是在调用一个返回自身的函数?
  • 意味着你的回调函数在for循环结束后被评估,给barChartID最后一个循环的值。
  • 这是一个函数工厂:你把你想记住的东西传递给外部函数,然后创建一个可以看到这些东西的内部函数,然后从外部函数返回。将来会有一种更标准化/可读性更高的方法(现在如果你破解它),即Function.prototype.bind
  • @bobince 嗨,根据你的回答,我希望你看看这个问题与stackoverflow.com/q/56397604/4425004 类似的问题。你能提供一个解决办法吗?
【解决方案2】:

这是因为您的回调函数有一个指向barChartID 变量的隐式指针。这就是闭包的工作原理。您想要的是在每次迭代中创建barChartID 的当前值的新副本。解决此问题的一种模式是在函数内运行 for 循环的主体。我在 John Resig 即将出版的一本书 Secrets of the JavaScript Ninja

中看到了这种模式
for(var i=0; i<barValues.length; i++) function(i){
    ...
}(i);

【讨论】:

    【解决方案3】:

    由于您使用的是 jQuery,因此您可以使用 $.each 函数来代替 for 循环。使用回调而不是循环将创建一个新的闭包。

    $.each(barValues, function(i, barValue){
        actualBarHeight = Math.floor((barValue/chartMaxY)*barchartHeight);
    
        var barChartID = "#barChart" + (i+1)
        $(barChartID + " .value span").css('background-color','transparent');
        $(barChartID + " img").animate({ 
                        height: actualBarHeight
                }, 500, function(){$(barChartID + " .value span").css('background-color','white');}
        );
    
        $(barChartID + " .value span").html("$"+Math.floor(barValue));
        $(barChartID + " .value").css("bottom",actualBarHeight+"px");
        $(barChartID + " .ylabel").html(chartMaxY);
    });
    

    【讨论】:

      【解决方案4】:

      您可以尝试缓存跨度选择器,如下所示:

      var barChartID = "#barChart" + (i+1)
      var span = $(barChartID + " .value span");
      span.css('background-color','transparent');
      $(barChartID + " img").animate({ 
          height: actualBarHeight
      }, 500, function() {
          span.css('background-color','white');
      });
      

      【讨论】:

      • 嗯...同样的结果。它只影响循环的最后一个跨度。
      • 如果我这样做: span.css('background-color','white');alert(span.css('background-color'); 然后我会收到 3 个警报(因为我m 循环 3 次)并且每次它为每个返回一个白色值。但是,只有循环中的最后一个以这种方式呈现。当我进入 fireBug 查看 3 个跨度中的每一个时,它们每个都有内联背景颜色...前两个是透明的,最后一个是白色的。很奇怪。
      猜你喜欢
      • 2012-08-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-08
      • 2020-05-06
      • 1970-01-01
      相关资源
      最近更新 更多