在JavaScript中回调传递变量参数的正确方法?

时间:2014-03-09 19:57:29

标签: javascript jquery ajax

我觉得这应该在互联网的某个地方得到解答,但是我找不到它,也许是因为我没有找到正确的术语,但这就是问题:我有以下功能:

function ParentFunction (DataBase, Parameters) {            
  for (k = 0; k < DataBase.length; k++){
    var CalendarURL = "https://www.google.com/calendar/feeds/" + DataBase.cid;
    $.ajax({
      url: CalendarURL,
      dataType: 'json',
      timeout: 3000,
      success: function( data ) { succesFunction(data, k, Parameters);},
      error: function( data ) { errorFunction ("Error",Parameters); }
    });
  }
}

我在succesFunction(data,k,Parameters)中遇到错误,因为'k'始终使用最新值进行评估。发生的事情是,当for循环运行k正确增加但是,当执行回调函数successFunction时,通常在循环结束后几毫秒,它总是用k的最后一个值计算,而不是$ .ajax被调用的循环。

我通过创建包含ajax调用的另一个函数来修复此问题。它看起来像这样:

function ParentFunction (DataBase, Parameters) {        
  for (k = 0; k < DataBase.length; k++){
    var CalendarURL = "https://www.google.com/calendar/feeds/" + DataBase.cid;
    AjaxCall(CalendarURL, k, Parameters);
  }
}

function AjaxCall(URL, GroupIndex, Parameters) {
    $.ajax({
      url: URL,
      dataType: 'json',
      timeout: 3000,
      success: function( data ) { succesFunction(data, GroupIndex, Parameters);},
      error: function( data ) { errorFunction ("Error",Parameters); }
    });
}

它有效。我认为当在parentFunction中调用函数时,会创建参数值的副本,并且当回调执行时,会看到此值而不是变量k,当时该值将具有错误的值。

所以我的问题是,这是实现这种行为的方式吗?或者有更合适的方法吗?我担心,不同的浏览器会采取不同的行为,并使我的解决方案在某些情况下工作而在其他情况下不起作用。

3 个答案:

答案 0 :(得分:3)

您遇到了javascript的常见问题:var变量是函数范围的,而不是块范围的。我将使用一个更简单的例子来重现同样的问题:

for(var i = 0; i < 5; i++) {
  setTimeout(function() { alert(i) }, 100 * i);
}

直观地说,你会得到0到4的警报,但实际上你会得到5的5,因为i变量由整个函数共享,而不仅仅是for块。 / p>

一种可能的解决方案是使for块成为一个函数:

for(var i = 0; i < 5; i++) {
  (function(local_i) {
    setTimeout(function() { alert(local_i); }, 100 * i);
  })(i);
}

不是最漂亮或更容易阅读。其他解决方案是完全创建一个单独的函数:

for(var i = 0; i < 5; i++) {
  scheduleAlert(i);
}

function scheduleAlert(i) {
  setTimeout(function() { alert(i); }, 100 * i);
}

在(希望接近)未来,当浏览器开始支持ES6时,我们将能够使用let而不是var,它具有块范围的语义而不会导致这种混乱。

答案 1 :(得分:1)

另一个选项 - 而不是创建一个名为的新函数 - 将使用partial application。 简单地说,部分应用程序是一个函数,它接受一个带有 n 参数的函数,以及应该部分应用的 m 参数,并返回一个函数( n - m )参数。

左侧部分应用程序的简单实现将是这样的:

var partial = (function() {
  var slice = Array.prototype.slice;
  return function(fn) {
    var args = slice.call(arguments,1);
    return function() {
      return fn.apply(this, args.concat(slice.call(arguments)));
    }
  }
}).call();

有了这个,那么你可以采用一个需要两个参数的函数,如:

function add(a,b) { return a + b; }

进入只需要一个参数的函数:

var increment = partial(add, 1);
increment(1);  // 2
increment(10); // 11

甚至是一个需要 no arugments的函数:

var return10 = partial(add, 5, 5);
return10(); // 10

这是一个简单的左侧部分应用程序函数,但是underscore.js提供了一个可以在参数列表中的任何位置部分应用参数的版本。

对于您的示例,您可以改为执行以下操作,而不是调用AjaxCall()来创建稳定的变量范围:

function ParentFunction (DataBase, Parameters) {            
  for (k = 0; k < DataBase.length; k++){
    var CalendarURL = "https://www.google.com/calendar/feeds/" + DataBase.cid;
    var onSuccess = _.partial(succesFunction, _, k, Parameters);
    $.ajax({
      url: CalendarURL,
      dataType: 'json',
      timeout: 3000,
      success: onSuccess,
      error: function( data ) { errorFunction ("Error",Parameters); }
    });
  }
}

在这里,我们使用_.partial()来转换具有以下符号的函数:

function(data, index, params) { /* work */ }

签名:

function(data) { /* work */ }

成功回调将使用实际调用哪个签名。


虽然可以肯定的是,对于已经描述的相同的基本概念来说,这几乎只是语法糖,但它有时在概念上有助于从功能角度而不是程序角度来思考这些问题。

答案 2 :(得分:0)

这与javascript中的闭包有关。你的匿名函数都引用了当前作用域之外的变量,因此每个函数的“k”都绑定到原始的循环变量“k”。由于这些函数在一段时间之后被调用,因此每个函数都会回顾看“k”是否位于其最后一个值。

最常见的解决方法就是你所做的。而不是在嵌套函数定义(强制闭包)中使用“k”,而是将其作为参数传递给外部函数,而不需要闭包。

以下是一些类似问题的帖子:

How do JavaScript closures work?

JavaScript closure inside loops – simple practical example

Javascript infamous Loop issue?