2011-05-25 139 views
20

我从来没有必须使用回调函数,所以我可能犯了一个完全愚蠢的错误。我想我在这里有点理解这个问题,但不知道如何解决它。将额外的参数传递给jQuery getJSON()成功回调函数

我的代码(有点简单)是:

for (var i = 0; i < some_array.length; i++) { 
    var title = some_array[i]; 
    $.getJSON('some.url/' + title, function(data) { 
     do_something_with_data(data, i); 
    } 

现在据我了解,这个匿名函数将仅的getJSON()已接收到的数据调用。但是到了这一点,i没有我需要的价值。或者,就我的观察而言,它具有循环完成后的最后一个值(不应该超出界限?)。

其结果是,如果数组的大小为6,do_something_with_data()将与值5

现在我想调用了五次,只是通过i匿名函数

function(data, i) { } 

但这似乎不可能。 i现在未定义。

回答

45

你需要了解什么是封闭是。在JavaScript中,如果在函数内部使用在外部上下文(外部函数或全局)中定义的变量,则围绕该变量创建一个闭包,该闭包将变量实例化并让函数在每次它继续引用它时被调用(以及任何其他函数实例在物品上都有闭包)。

因为原来的变量仍然实例化,如果你改变的变量任何地方代码,值的功能运行时以后就会有电流变化值,值当函数是第一创建。

之前,我们的地址进行关闭工作的权利,请注意,在循环中重复声明title变量不工作(其实,你能想到的变量作为基本为的悬挂function的scope- - 不像其他语言,for JavaScript中的循环没有范围,因此变量只声明一次,并且而不是在循环内声明或重新声明。在循环之外声明变量应该有助于澄清为什么你的代码不能像你期望的那样工作。

由于,当回调运行,因为他们有过相同的变量i封闭,他们都当i增量和它们在运行时将全部使用的i当前值的影响(这将是错误的正如你发现的那样,因为回调在之后运行,该回路已经完全完成创建回调)。异步代码(例如JSON调用响应)不会运行,直到所有同步代码完成执行为止 - 因此在执行任何回调之前,循环将保证完成。

要解决这个问题,你需要一个新的功能来运行具有其自己的范围,这样在循环内声明的回调,有超过每不同值的新闭合。你可以用一个单独的函数来做到这一点,或者只是在回调参数中使用一个被调用的匿名函数。这里有一个例子:

var title, i; 
for (i = 0; i < some_array.length; i += 1) { 
    title = some_array[i]; 
    $.getJSON(
     'some.url/' + title, 
     (function(thisi) { 
      return function(data) { 
      do_something_with_data(data, thisi); 
      // Break the closure over `i` via the parameter `thisi`, 
      // which will hold the correct value from *invocation* time. 
      }; 
     }(i)) // calling the function with the current value 
    ); 
} 

为清楚起见,我会打破它到一个单独的功能,所以你可以看到发生了什么事情:

function createCallback(item) { 
    return function(data) { 
     do_something_with_data(data, item); 
     // This reference to the `item` parameter does create a closure on it. 
     // However, its scope means that no caller function can change its value. 
     // Thus, since we don't change `item` anywhere inside `createCallback`, it 
     // will have the value as it was at the time the createCallback function 
     // was invoked. 
    }; 
} 

var title, i, l = some_array.length; 
for (i = 0; i < l; i += 1) { 
    title = some_array[i]; 
    $.getJSON('some.url/' + title, createCallback(i)); 
    // Note how this parameter is not a *reference* to the createCallback function, 
    // but the *value that createCallback() returns*, which is itself a function. 
} 

注:因为你的阵列显然只具有职称的话,您可以考虑使用title变量而不是i,这需要您返回some_array。但无论哪种方式都有效,你知道你想要什么。考虑这个回调-创建功能(或者匿名一个或createCallback一个)在本质上转换i可变成单独thisi变量的,通过每次引入一个新的

一种可能有用的方式函数有自己的范围。也许可以这样说,“参数将数值从关闭中分解出来”。

只是要小心:这种技术不会复制它们,因为对象是引用类型。仅仅将它们作为参数传递将不会产生事后不能改变的东西。你可以复制一个你喜欢的街道地址,但这不会创建一个新房子。如果你想要一个导致不同事物的地址,你必须建造一座新房子。

6

您可以创建使用即时功能关闭(一个执行的时候了),返回另一个功能:

for (var i = 0; i < some_array.length; i++) { 
    var title = some_array[i]; 
    $.getJSON('some.url/' + title, (function() { 
     var ii = i; 
     return function(data) { 
      do_something_with_data(data, ii); 
     }; 
    })()); 
} 
+1

+1除了@Chris想要保留对每个“i”的引用。 – Jeremy 2011-05-25 18:38:32

+0

噢,谢谢,我错过了。新增了它。 – patorjk 2011-05-25 18:42:59

+0

我试图去适应这个问题,但是**数据现在变成了什么值?现在对我来说是不确定的。 – Chris 2011-05-25 18:52:44

1

创建n个瓶盖,并通过在“我”每一次,像这样的价值:

var i, title; 
for (i = 0; i < some_array.length; i++) { 
    title = some_array[i]; 
    $.getJSON('some.url/' + title, (function(i_copy) { 
     return function(data) { 
      do_something_with_data(data, i_copy); 
     }; 
    })(i)); 
} 
+0

请注意,jslint会要求您在循环外部移动var声明,并将匿名函数调用放在括号内,而不是外部。 – ErikE 2011-05-25 18:54:22

0

我觉得有些浏览器在使用的同时进行多个异步调用的麻烦,所以你可以让他们一次一个:

var i; 
function DoOne(data) 
{ 
    if (i >= 0) 
     do_something_with_data(data, i); 
    if (++i >= some_array.length) 
     return; 
    var title = some_array[i]; 
    $.getJSON('some.url/' + title, DoOne); 
} 

// to start the chain: 
i = -1; 
DoOne(null); 
+2

请勿使用'async:false'。它会导致** whole **浏览器(包括UI线程)在网络请求期间锁定。如果你冻结浏览器,你的用户将会非常不高兴。 – josh3736 2011-05-25 19:25:04

+0

Firefox和Chrome只锁定了当前的选项卡,但指出,我从我的答案中删除了这一点。 – 2011-05-25 20:29:24

3

如果你能修改在some.url服务,这将是如果不是为some_array中的每个项目分别发出一个HTTP请求,只需在单个HTTP请求中传递数组中的每个项目,那么会更好。

$.getJSON('some.url', { items: some_array }, callback); 

您的数组将被JSON序列化并发布到服务器。假设some_array是一个字符串数组,该请求将是这样的:

POST some.url HTTP/1.1 
... 

{'items':['a','b','c', ... ]} 

你的服务器脚本则应该反序列化请求主体和环比items阵列中的每个项目的JSON请求,返回一个JSON序列化响应数组。

HTTP/1.1 200 OK 
... 

{'items':[{id:0, ... }, {id:1, ... }, ... ]} 

(或者是任何数据你回来了。)如果你的反应项目在相同的顺序要求的项目,很容易块的东西重新走到一起。在您的成功回调中,只需将物品索引与some_array的索引匹配即可。全部放在一起:

$.getJSON('some.url', { items: some_array }, function(data) { 
    for (var i = 0; i < data.items.length; i++) { 
     do_something_with_data(data.items[i], i); 
    } 
}); 

通过“配料了”您的要求为这样一个HTTP请求,你会显著提高性能。考虑一下,如果每个网络的往返时间至少需要200ms,而且有5个项目,那么你至少要看到1秒的延迟。通过一次请求它们,网络延迟保持恒定200ms。 (很明显,随着请求量的增加,服务器脚本执行和网络传输时间将会发挥作用,但是性能仍然会比如果您为每个项目发出单独的HTTP请求更好一个量级)。

+1

这些都是好点。这可能是最好的结合请求。 – ErikE 2011-05-26 16:14:43

0

我刚好与OP相同的问题,但以不同的方式解决问题。我用jQuery $替换了我的JavaScript for'循环'。每次迭代都会调用一个函数,我认为这个函数可以解决回调'时序'问题。我将外部数据数组组合到一个JavaScript对象中,以便我可以引用传递给JSON URL的参数和该对象的同一元素中的其他字段。我的对象元素使用PHP从mySQL数据库表中出来。

var persons = [ 
{ Location: 'MK6', Bio: 'System administrator' }, 
{ Location: 'LU4', Bio: 'Project officer' }, 
{ Location: 'B37', Bio: 'Renewable energy hardware installer' }, 
{ Location: 'S23', Bio: 'Associate lecturer and first hardware triallist' }, 
{ Location: 'EH12', Bio: 'Associate lecturer with a solar PV installation' } 
]; 

function initMap() { 
    var map = new google.maps.Map(document.getElementById('map_canvas'), { 
    center: startLatLon, 
    minZoom: 5, 
    maxZoom: 11, 
    zoom: 5 
    }); 
    $.each(persons, function(x, person) { 
    $.getJSON('http://maps.googleapis.com/maps/api/geocode/json?address=' + person.Location, null, function (data) { 
     var p = data.results[0].geometry.location; 
     var latlng = new google.maps.LatLng(p.lat, p.lng); 
     var image = 'images/solarenergy.png'; 
     var marker = new google.maps.Marker({ 
     position: latlng, 
     map: map, 
     icon: image, 
     title: person.Bio 
     }); 
     google.maps.event.addListener(marker, "click", function (e) { 
     document.getElementById('info').value = person.Bio; 
     }); 
    }); 
    }); 
} 
相关问题