我以前从未使用过回调函数,所以我可能犯了一个完全愚蠢的错误。我想我有点理解这里的问题,但不知道如何解决它。
我的代码(稍微简化)是:
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) { }
但这似乎不可能。 我现在未定义。
答案 0 :(得分:45)
您需要了解闭包是什么。在javascript中,当你在一个函数内部使用一个在外部上下文(外部函数或全局函数)中定义的变量时,你会围绕该变量创建一个闭包,它保持变量的实例化,并让函数每次都继续引用它。被调用(以及项目上有闭包的任何其他函数实例)。
因为原始变量仍然是实例化的,如果你在代码中更改了 where 的变量值,当函数稍后运行它将具有当前更改的值,不首次创建函数时的值。
在我们解决关闭工作正确之前,请注意在循环中重复声明title
变量不起作用(实际上,您可以认为变量基本上是悬挂进入function
的范围 - 与其他语言不同,JavaScript中的for
循环没有范围,因此该变量仅为函数声明一次并且< em> not 在循环内声明或重新声明)。在循环外声明变量应该有助于澄清为什么代码无法按预期工作。
按原样,当回调运行时,因为它们对同一个变量i
有一个闭包,当i
递增时它们都会受到影响,并且它们都将使用当前 i
运行时的值(如果你发现这将是错误的,因为回调在循环完成后完成创建回调后运行)。异步代码(例如JSON调用响应)在所有同步代码完成执行之前不会运行也不会运行 - 所以在执行任何回调之前保证循环完成。
要解决这个问题,你需要一个新的函数来运行它的拥有范围,这样在循环内部声明的回调中,每个不同的> 价值。您可以使用单独的函数执行此操作,或者只在callback参数中使用调用的匿名函数。这是一个例子:
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
变量。也许可以说“参数打破了封闭的价值”。
请注意:由于对象是引用类型,因此在不复制对象的情况下,此技术不适用于对象。仅仅将它们作为参数传递将不会产生事后不能改变的东西。您可以复制街道地址,但这不会创建新房。如果你想要一个能够带来不同的地址,你必须建造一所新房子。
答案 1 :(得分: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);
};
})());
}
答案 2 :(得分: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', ... ]}
然后,您的服务器脚本应该从请求主体反序列化JSON请求,并循环遍历items
数组中的每个项目,并返回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请求中,您将显着提高性能。考虑一下,如果每个网络往返至少花费200毫秒,有5个项目,那么您至少要看1秒延迟。通过立即请求它们,网络延迟保持恒定200ms。 (显然,对于更大的请求,服务器脚本执行和网络传输时间将会发挥作用,但性能仍将比为每个项目发出单独的HTTP请求更好。)
答案 3 :(得分:1)
创建N个闭包并每次传递'i'的值,如下所示:
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));
}
答案 4 :(得分: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);
答案 5 :(得分:0)
我和OP有完全相同的问题但是以不同的方式解决了它。我用jQuery $ .each替换了我的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;
});
});
});
}