繁体   English   中英

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

[英]Correct way to pass a variable argument in a callback in JavaScript?

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

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,当时该值将具有错误的值。

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

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

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

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

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

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 ,它具有块范围的语义,并且不会导致这种混淆。

另一个选择 - 而不是创建一个新的命名函数 - 将使用部分应用程序 简单地说,部分应用程序是一个函数,它接受一个带有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

甚至是一个不需要考虑的功能:

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 */ }

哪个是成功回调实际上将被调用的签名。


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

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

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

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

JavaScript闭包如何工作?

循环内的JavaScript闭包 - 简单实用的例子

Javascript臭名昭着的循环问题?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM