你需要了解什么是闭包。在JavaScript中,每个变量的作用域都有特定的规则:
- 隐式声明或使用var声明的变量的作用域为最近/当前的函数(包括“箭头函数”),如果不在函数内,则为window或适用于执行上下文的其他全局对象(例如在Node.js中为global)。
- 使用let或const声明的变量(在ES5及以上版本中)的作用域为最近的语句块{ /* 不是对象,而是任何可执行语句的地方 */ }。
如果任何代码可以访问当前作用域或任何父级作用域中的变量,这将在该变量周围创建一个闭包,使变量保持活动状态,并保持任何由该变量引用的对象实例化,以便这些父级或内部函数或块可以继续引用变量并访问其值。
因为原始变量仍然处于活动状态,如果稍后在代码的任何地方更改该变量的值,那么当具有对该变量的闭包的代码稍后运行时,它将具有更新/更改后的值,而不是函数或作用域创建时的值。
现在,在我们解决闭包问题之前,请注意在循环中重复声明没有使用let或const的title变量是不起作用的。var变量被提升到最近函数的作用域中,并且未使用var分配的变量(不引用任何函数作用域)会被隐式附加到全局作用域,而在JavaScript中没有作用域的for循环,因此在其中声明的变量实际上只声明了一次,尽管看起来在循环内(重新)声明。将变量声明放在循环外应该有助于澄清您的代码为什么不能按预期工作。
现在的问题是,当回调运行时,因为它们对相同的变量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);
};
}(i))
);
}
为了更清晰,我将其分成一个单独的函数,这样你就可以看到发生了什么:
function createCallback(item) {
return function(data) {
do_something_with_data(data, item);
};
}
var title, i, l = some_array.length;
for (i = 0; i < l; i += 1) {
title = some_array[i];
$.getJSON('some.url/' + title, createCallback(i));
}
注意:由于你的数组显然只有标题,因此你可以考虑使用
title
变量来替代
i
,这样就不需要回到
some_array
了。但是无论哪种方式都可以,你知道自己想要什么。
一种潜在有用的思考方式是,回调函数创建函数(无论是匿名函数还是
createCallback
函数)本质上将
i
变量的
值转换为单独的
thisi
变量,每次引入具有自己作用域的新函数。也许可以说,“参数将值从闭包中分离出来”。
只需小心:这种技术对于没有复制的对象不起作用,因为对象是引用类型。仅仅将它们作为参数传递并不会产生不可更改的结果。你可以随意复制一个街道地址,但这并不会创建一个新房子。如果你想要通向不同地方的地址,必须建造一个新房子。