使用var
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function() {
console.log(i);
};
}
a[6](); // 10
使用let
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function() {
console.log(i);
};
}
a[6](); // 6
我不明白为什么结果不同。有人能指点我吗?
结果数组由函数组成,每个函数体看起来像这样:
console.log(i);
i
的值取决于我们是使用var
还是let
来声明变量。
var
(ECMAScript 5 and 6)
这里的i
是一个全局变量,在退出循环后其值为10
。这是记录的值。
let
(ECMAScript 6)
这里i
是一个局部变量,其作用域仅限于for
语句。此外,该变量在每次迭代时都获得一个新的绑定。最好的解释是你的代码翻译成ECMAScript 5:
"use strict";
var a = [];
var _loop = function(i) {
a[i] = function() {
console.log(i);
};
};
for (var i = 0; i < 10; i++) {
_loop(i);
}
a[6](); // 6
例如,在第七次迭代时,i
的值将是6
(从0开始计数)。在迭代内部创建的函数将引用此值。
我认为最好不要在循环中定义函数,您可以通过一个返回闭包的函数定义轻松实现这一点:
function logNumber(num) {
return function() {
console.log(num);
}
}
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = logNumber(i);
}
a[6]();
关于两个示例之间的区别,一个是使用let
进行块作用域。一个更好的例子是:
ECMA5:
for (var i = 0; i < 10; i++) { }
console.log(i); // 10
ECMA6:
for (let i = 0; i < 10; i++) { }
console.log(i); // i is not defined
Edit:正如我在对您的问题的评论中所述,这更可能是您正在使用的转译器的副作用。Firefox支持块作用域,并且两个版本的循环都产生10
作为输出(它们应该这样做)。
根据规范,这是正确的行为,var
和let
的行为被定义为不同。
参见规范,网址:https://people.mozilla.org/~jorendorff/es6-draft.html#sec-forbodyevaluation。据此,使在循环内声明的函数接近于块作用域循环索引的当前值的相关概念被称为"每次迭代绑定"one_answers"每次迭代环境"。
Babel会正确处理它,生成以下代码:
var a = [];
var _loop = function (i) {
a[i] = function () {
console.log(i);
};
};
for (var i = 0; i < 10; i++) {
_loop(i);
}
这通过将for
循环的内容隔离到一个由索引参数化的单独函数中来实现for (let
的语义。通过这样做,函数不再关闭for循环索引,并且在创建的每个函数中分别处理i
。因此答案是6。
Traceur不能产生正确的结果。结果是10。
所以那个在So上被问了100次的著名问题,为什么我的函数在循环中声明并关闭索引索引使用了循环索引的"错误"值,应该不再被问了?
这个问题比仅仅声明"当然,let
是块作用域"更微妙。我们知道。例如,我们在if
块中了解它是如何工作的。但这里发生的事情是for
上下文中的块作用域的一点扭曲,迄今为止包括我在内的许多人都不知道。它实际上是一个在"块"(如果您将该块视为for
语句的主体)之外声明的变量,但在循环的每次迭代中都有单独存在。
更多信息请参见https://github.com/babel/babel/issues/1078
为什么ES6和ES5的结果不同?
因为let
和var
不同。let
是块作用域,而var
是函数作用域。
在您的第一个示例中,只有一个变量i
。您创建的每个函数都有对相同变量i
的引用。在调用a[6]()
时,i
的值为10
,因为这是循环的终止条件。
在第二个例子中,循环的每次迭代都有自己的变量i
。