您需要了解 闭包 是什么。在 JavaScript 中,每个变量的作用域都有一定的规则。
- 隐式声明或使用
var 声明的变量的范围是最近/当前function(包括“箭头函数”),或者如果不在函数中,则为window 或其他适合执行的全局对象上下文(例如,在 Node 中,global)。
- 使用
let 或const(在ES5 及更高版本中)声明的变量的作用域是最近的语句块{ /* not an object, but any place that will take executable statements here */ }。
如果任何代码可以访问当前作用域或任何父作用域中的变量,则会在该变量周围创建一个闭包,使变量保持活动状态并保持变量引用的任何对象实例化,以便这些父或内部函数或块可以继续引用变量并访问该值。
因为原始变量仍然处于活动状态,如果您稍后在代码中更改该变量的值anywhere,那么当稍后运行对该变量具有闭包的代码时,它将具有更新/更改value,不是第一次创建函数或作用域时的值。
现在,在我们解决使闭包正常工作之前,请注意,在循环中重复声明 title 变量而不使用 let 或 const 是行不通的。 var 变量被提升到最近的函数的作用域中,并且没有分配var 且不引用任何函数作用域的变量被隐式附加到全局作用域,即window in a浏览器。在const 和let 存在之前,JavaScript 中的for 循环没有作用域,因此在其中声明的变量实际上只声明一次,尽管似乎在循环内(重新)声明了。在循环外声明变量应该有助于您了解为什么您的代码没有按预期工作。
事实上,当回调运行时,因为它们对同一个变量 i 有一个闭包,所以它们都会在 i 递增时受到影响,并且它们都将使用 @ 的 current 值987654339@ 当它们运行时(您发现这是不正确的,因为回调都在循环完全完成创建它们之后运行)。异步代码(例如 JSON 调用响应)在所有同步代码完成执行之前不会也不能运行——因此循环保证在任何回调执行之前完成。
要解决这个问题,您需要运行一个具有其自己的范围的新函数,以便在循环内声明的回调中,每个不同都有一个新的闭包em> 值。您可以使用单独的函数来执行此操作,或者仅在回调参数中使用调用的匿名函数。这是一个例子:
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 变量的值 转换为单独的@ 987654347@ 变量,通过每次引入一个具有自己作用域的新函数。或许可以说“参数打破了闭包中的值”。
请注意:如果不复制对象,此技术将无法对对象起作用,因为对象是引用类型。仅仅将它们作为参数传递不会产生事后无法更改的东西。您可以随意复制街道地址,但这不会创建新房子。如果你想要一个通往不同地方的地址,你必须建造一座新房子。