这个问题是几个不同的(我认为相关的)问题,我在下面给出,但总的来说,我试图理解 David Flanagan 的 O'Reilly JavaScript 书中的一段代码,该书中给出了一个使用循环创建闭包的示例无意中:
function constfuncs() {
let funcs = [];
for(var i = 0; i < 10; i++) {
funcs[i] = () => i;
}
return funcs;
}
let funcs = constfuncs();
funcs[5]() //10, rather than 5
上面的代码示例旨在让
funcs
数组的每个元素成为一个函数,可以在不带参数的情况下调用该函数,以返回一个在数字上等于其在 funcs
中的索引的值;然而,它并没有成功做到这一点。我想我很大程度上理解为什么会这样:var i
,因为它是使用var
声明的,其作用域是函数constfuncs
,而不是那个for
循环的块,因此它是相同的 i
被存储在 funcs
中的每个函数使用,并且 i
在该循环中以 10
结尾,因此 funcs
中的十个返回函数中的任何一个在调用时将始终评估为 10
。
本书的下一页指出,要解决上述问题,只需进行一行更改:只需将
i
设为块作用域即可,因此在 let i
中使用 var i
而不是 for
循环,如下:
function constfuncs() {
let funcs = [];
for(let i = 0; i < 10; i++) {
funcs[i] = () => i;
}
return funcs;
}
let funcs = constfuncs();
funcs[5]() //is now 5, as desired
但是,我不明白这是如何解决问题的。对我来说,
i
似乎仍然只存在一次,但十个创建的函数中的每一个似乎都可以访问内存中i
的自己的版本/副本。如果在 for
循环中声明了一个新的块范围局部变量,并在每次迭代时为其分配了 i
的值,然后使用 i
作为 funcs
的每个函数返回的值,则如果在每次迭代时都重新声明循环中声明的块范围局部变量(如使用新内存),也许这对我来说更有意义;但即便如此, i
不是,但上面的内容仍然可以按预期工作。这引出了我的问题:
let
和 const
是块作用域,循环的每次迭代都定义了一个独立于所有其他迭代的范围的范围,并且每个迭代都定义了一个范围。范围有自己独立的 i
绑定。”问题 1-2 是相关的,问题 3-4 也是相关的;一切都与上面的例子有关。欢迎任何其他想法来解释为什么上面的第二种方法(带有
let i
)成功地修复了解决方案。
从技术上讲,我不会回答你们任何问题,但我可以解释您想知道“如何解决问题”的部分:
let
声明的变量 被视为在循环“主体” 中声明,根据定义。这是带有 let
的 for 循环的特殊行为。
另一方面,用
var
声明的变量与定义 for 循环的范围具有相同的范围(即在循环“主体”之外)。
因此,如果您在 for 循环中声明
let i
outside,您将再次得到错误的值 10
(如您所料):
function constfuncs(){
let funcs = [];
let i;
for( i = 0; i < 10; i++ ){
funcs[ i ] = function(){
return i;
};
}
return funcs;
}
另一方面,即使您将
var
保留在循环初始化中,但在循环体中使用 let
声明一个 new 变量,您也会得到所需的值
5
:
function constfuncs(){
let funcs = [];
for( var i = 0; i < 10; i++ ){
let value = i; // <-- new variable in every iteration
funcs[ i ] = () => value;
}
return funcs;
}
较新的
let
声明是专门为这种行为设计的,因为人们往往对 var
的行为感到困惑。
(我认为你想知道:IMO
let
有太多特殊情况。当你不思考它时,
let
使行为更加直观,但如果你想理解它,就会变得更复杂。但它仍然解决了var
的重要缺陷。)