Javascript 将额外参数传递给 jQuery getJSON() 成功回调函数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6129145/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Pass extra parameter to jQuery getJSON() success callback function
提问by Chris
I've never had to use callback functions before, so I may have made a completely stupid mistake. I think I somewhat understand the problem here, but not how to solve it.
我以前从未使用过回调函数,所以我可能犯了一个完全愚蠢的错误。我想我有点理解这里的问题,但不知道如何解决它。
My code (a bit simplified) is:
我的代码(有点简化)是:
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);
}
Now as far as I understand, this anonymous function will only be called if getJSON() has received the data. But by this point, i
does not have the value I would require. Or, as far as my observation goes, it has the last value it would have after the loop is done (shouldn't it be out of bounds?).
现在据我所知,只有当 getJSON() 收到数据时才会调用这个匿名函数。但此时,i
没有我需要的价值。或者,就我的观察而言,它具有循环完成后的最后一个值(不应该超出范围吗?)。
As a result, if the array had a size of 6, do_something_with_data()
would be called five times with the value 5.
因此,如果数组的大小为 6,do_something_with_data()
则将使用值 5 调用五次。
Now I thought, just pass i
to the anonymous function
现在我想,只要传递i
给匿名函数
function(data, i) { }
but this does not seem to be possible. iis undefined now.
但这似乎是不可能的。我现在未定义。
回答by ErikE
You need to understand what a closureis. In javascript, when inside a function you use a variable that was defined in an outer context (outer function or global), you create a closure around that variable, which keeps the variable instantiated and lets the function continue to refer to it each time it is invoked (as well as any other function instances with a closure on the item).
您需要了解什么是闭包。在 javascript 中,当您在函数内部使用在外部上下文(外部函数或全局)中定义的变量时,您会在该变量周围创建一个闭包,从而保持该变量的实例化并让该函数在每次调用时继续引用它被调用(以及任何其他在项目上带有闭包的函数实例)。
Because the original variable is still instantiated, if you change the value of that variable anywherein the code, when the function runs later it will have the current changed value, notthe value when the function was first created.
因为原始变量仍然是实例化的,如果你在代码中的任何地方改变那个变量的值,当函数稍后运行时,它会有当前改变的值,而不是函数第一次创建时的值。
Before we address making the closure work right, note that declaring the title
variable repeatedly in the loop doesn't work (in fact, you can think of the variable as essentially being hoistedinto the function
's scope--unlike some other languages, for
loops in JavaScript have no scope, therefore the variable is declared only oncefor the function and is notdeclared or redeclared inside the loop). Declaring the variable outside the loop should help clarify for you why your code isn't working as you'd expect.
在我们解决如何使闭包正常工作之前,请注意title
在循环中重复声明变量是行不通的(实际上,您可以认为变量本质上是被提升到function
的作用域中——与其他一些语言不同,for
循环在JavaScript 没有作用域,因此该变量只为函数声明一次,并且不会在循环内声明或重新声明)。在循环外声明变量应该有助于向您阐明为什么您的代码没有按预期工作。
As is, when the callbacks run, because they have a closure over the same variable i
, they are all affected when i
increments and they will all use the currentvalue of i
when they run (which will be wrong as you discovered, because the callbacks run afterthe loop has completely finished creating the callbacks). Asynchronous code (such as the JSON call response) does not and cannot run until all synchronous code finishes executing--so the loop is guaranteed to complete before any callback is ever executed.
照原样,当回调运行时,因为它们对同一个变量有一个闭包i
,所以它们在i
递增时都会受到影响,并且它们都将使用它们运行时的当前值i
(正如您发现的那样,这将是错误的,因为回调在循环已完全完成创建回调)。异步代码(例如 JSON 调用响应)在所有同步代码完成执行之前不会也不能运行——因此循环保证在执行任何回调之前完成。
To get around this you need a new function to run that has its ownscope so that in the callbacks declared inside of the loop, there is a new closure over each differentvalue. You could do that with a separate function, or just use an invoked anonymous function in the callback parameter. Here's an example:
为了解决这个问题,你需要一个新函数来运行,它有自己的作用域,这样在循环内声明的回调中,每个不同的值都有一个新的闭包。你可以用一个单独的函数来做到这一点,或者只在回调参数中使用一个被调用的匿名函数。下面是一个例子:
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
);
}
For clarity I'll break it out into a separate function so you can see what's going on:
为了清楚起见,我将把它分解成一个单独的函数,以便您可以看到发生了什么:
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.
}
Note: since your array apparently only has titles in it, you could consider using the title
variable instead of i
which requires you to go back to some_array
. But either way works, you know what you want.
注意:由于您的数组显然只有标题,您可以考虑使用title
变量而不是i
需要您返回some_array
. 但无论哪种方式都有效,您知道自己想要什么。
One potentially useful way to think about this that the callback-creating function (either the anonymous one or the createCallback
one) in essence converts the valueof the i
variable into separate thisi
variables, via each time introducing a new function with its own scope. Perhaps it could be said that "parameters break values out of closures".
一种可能有用的方式来看待这个回调-创建功能(或者匿名一个或createCallback
本质的一个)转换值所述的i
可变成单独thisi
通过每次引入具有自己范围的新函数的变量。也许可以说“参数打破了闭包的价值”。
Just be careful: this technique will not work on objects without copying them, since objects are reference types. Merely passing them as parameters will not yield something that cannot be changed after the fact. You can duplicate a street address all you like, but this doesn't create a new house. You must build a new house if you want an address that leads to something different.
请注意:如果不复制对象,此技术将无法用于对象,因为对象是引用类型。仅仅将它们作为参数传递不会产生事后无法更改的东西。您可以随心所欲地复制街道地址,但这不会创建新房子。如果你想要一个能带来不同的地址,你必须建造一所新房子。
回答by patorjk
You could create a closure using an immediate function (one that executes right away) that returns another function:
您可以使用返回另一个函数的立即函数(立即执行的函数)创建闭包:
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);
};
})());
}
回答by josh3736
If you can modify the service at some.url
, it would be muchbetter if rather than making a separate HTTP request for each item in some_array
, you simply passed every item in the array in a single HTTP request.
如果你能在修改服务some.url
,这将是很多更好,如果不是做对每个项目单独的HTTP请求some_array
,你只需在数组中传递一个HTTP请求的每个项目。
$.getJSON('some.url', { items: some_array }, callback);
Your array will be JSON serialized and POSTed to the server. Assuming some_array
is an array of strings, the request will look like this:
您的数组将被 JSON 序列化并发布到服务器。假设some_array
是一个字符串数组,请求将如下所示:
POST some.url HTTP/1.1
...
{'items':['a','b','c', ... ]}
Your server script should then deserialize the JSON request from the request body and loop over each item in the items
array, returning a JSON-serialized array of responses.
然后,您的服务器脚本应该反序列化来自请求正文的 JSON 请求并遍历items
数组中的每个项目,返回一个 JSON 序列化的响应数组。
HTTP/1.1 200 OK
...
{'items':[{id:0, ... }, {id:1, ... }, ... ]}
(Or whatever data it is you're returning.) If your response items are in the same order as the request items, it is easy to piece things back together. In your success callback, simply match the item index with some_array
's index. Putting it all together:
(或者您要返回的任何数据。)如果您的响应项与请求项的顺序相同,则很容易将它们重新组合在一起。在您的成功回调中,只需将项目索引与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);
}
});
By 'batching up' your requests into a single HTTP request like this, you'll significantlyimprove performance. Consider that if each network round-trip takes at least 200ms, with 5 items, you're looking at a minimum 1 second delay. By requesting them all at once, network delay stays a constant 200ms. (Obviously with larger requests, server script execution and network transfer times will come in to play, but performance will still be an order of a magnitude better than if you issue a separate HTTP request for each item.)
通过像这样将您的请求“批处理”成单个 HTTP 请求,您将显着提高性能。考虑到如果每个网络往返至少需要 200 毫秒,有 5 个项目,那么您将看到至少 1 秒的延迟。通过一次请求它们,网络延迟保持恒定 200 毫秒。(显然,对于较大的请求,服务器脚本执行和网络传输时间将发挥作用,但性能仍然比为每个项目发出单独的 HTTP 请求要好一个数量级。)
回答by digitalbath
Create N closures and pass in the value of 'i' each time, like so:
创建 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));
}
回答by Jason Goemaat
I think some browsers have trouble with making multiple asynchronous calls at the same time, so you could make them one at a time:
我认为某些浏览器在同时进行多个异步调用时会遇到麻烦,因此您可以一次进行一个调用:
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);
回答by Chris Valentine
I had exactly the same issue as the OP but solved it a different way. I replaced my JavaScript 'for' loop with a jQuery $.each which for each iteration calls a function which I think gets over the callback 'timing' issue. And I combined my external data arrays into a JavaScript object so that I could reference both the parameter I was passing on the JSON URL and the other field in the same element of that object. My object elements came out of a mySQL database table using PHP.
我遇到了与 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;
});
});
});
}