这个问题在这里已经有了答案:
JavaScript closure inside loops – simple practical example
(44 个答案)
去年关闭。
我以前从未使用过回调函数,所以我可能犯了一个完全愚蠢的错误。我想我有点理解这里的问题,但不知道如何解决它。
我的代码(有点简化)是:
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
没有我需要的值(value)。或者,据我观察,它具有循环完成后的最后一个值(它不应该超出范围吗?)。结果,如果数组的大小为 6,
do_something_with_data()
将被调用五次,值为 5。现在我想,只要通过
i
到匿名函数function(data, i) { }
但这似乎是不可能的。 我 现在是未定义的。
最佳答案
你需要了解什么是闭包。在 JavaScript 中,每个变量的作用域都有一定的规则。
var
声明的变量的范围是最近的/当前的function
(包括“箭头函数”),或者如果不在函数中,则 window
或其他适用于执行上下文的全局对象(例如,在 Node 中,global
)。 let
声明的变量的范围或 const
(在 ES5 及更高版本中)是最近的语句 block { /* not an object, but any place that will take executable statements here */ }
. 如果任何代码可以访问当前作用域或任何父作用域中的变量,则会在该变量周围创建一个闭包,使变量保持事件状态并保持变量引用的任何对象实例化,以便这些父函数或内部函数或 block 可以继续引用变量并访问值。
因为原始变量仍然处于事件状态,如果您稍后在代码中的任何位置更改该变量的值,那么当稍后运行对该变量具有闭包的代码时,它将具有更新/更改的值,而不是函数或作用域时的值最初创建。
现在,在我们解决使闭包正常工作之前,请注意声明
title
不带 let
的变量或 const
在循环中反复不起作用。 var
变量被提升到最近的函数的作用域中,并且分配的变量没有 var
不引用任何函数作用域的会隐式附加到全局作用域,即 window
在浏览器中。之前 const
和 let
存在,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 invoking createCallback() returns*, which is a function taking one `data` parameter.
}
注意:由于您的数组显然只有标题,您可以考虑使用 title
变量而不是 i
这需要您返回 some_array
.但无论哪种方式都有效,你知道你想要什么。考虑这一点的一种可能有用的方法是,创建回调的函数(匿名函数或
createCallback
函数)本质上转换了 i
的值。变量成单独的thisi
变量,通过每次引入一个具有自己作用域的新函数。也许可以说“参数打破了闭包的值(value)”。请注意:如果不复制对象,此技术将无法对对象起作用,因为对象是引用类型。仅仅将它们作为参数传递不会产生事后无法更改的东西。您可以随意复制街道地址,但这不会创建新房子。如果您想要一个通向不同事物的地址,您必须 build 一座新房子。
关于javascript - 将额外参数传递给 jQuery getJSON() 成功回调函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6129145/