为什么结果不同(使用var vs. let)?

时间:2015-05-21 06:20:51

标签: javascript arrays ecmascript-6 closures

这使用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

我不明白为什么结果不同。有人可以指导我吗?

4 个答案:

答案 0 :(得分:5)

结果数组由函数组成,每个函数体如下所示:

console.log(i);

i的值取决于我们是使用var还是let来声明变量。

var(ECMAScript 5和6)

此处i是一个全局变量,在退出循环后其值为10。这是记录的值。

let(ECMAScript 6)

此处i是一个局部变量,其范围仅限于for语句。此外,此变量在每次迭代时都会获得新的绑定。最好用您的代码transpiled to 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(从零开始计算)。在迭代内创建的函数将引用此值。

答案 1 :(得分:2)

我认为不在循环中定义函数会好得多,您可以使用一个返回closure的函数定义轻松完成此任务:

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

修改:正如我在您对您的问题的评论中所述,这更可能是您正在使用的转换器的副作用。 Firefox支持块作用域,并且两个版本的循环都会生成10作为输出(他们应该这样做)。

答案 2 :(得分:2)

根据规范,这是正确的行为。 varlet的行为定义为不同。

请参阅https://people.mozilla.org/~jorendorff/es6-draft.html#sec-forbodyevaluation处的规范。根据这一点,使循环内部声明的函数接近块作用域循环索引的当前值的相关概念称为“每次迭代绑定”和“每次迭代环境”。

Babel正确处理它,产生以下代码:

var a = [];
var _loop = function (i) {
    a[i] = function () {
        console.log(i);
    };
};

for (var i = 0; i < 10; i++) {
    _loop(i);
}

这通过将for (let循环的内容隔离到由索引参数化的单独函数来实现for的语义。通过这样做,函数不再关闭for循环索引,并且在创建的每个函数中单独处理i。因此答案是6。

Traceur无法产生正确的结果。它产生10。

因此,在SO上被问过100次的着名问题,关于我的函数在循环中声明并关闭索引索引的原因是使用循环索引的“错误”值,不应再被问到了吗?

这个问题稍微有些细微之处,只是宣称“当然,let是块范围的”。我们知道。例如,我们了解它在if块中的工作原理。但是这里发生的事情是for背景下的块范围有点扭曲,包括我在内的很多人都不知道。它是一个变量,实际上声明之外(如果您认为该块是for语句的主体),但每个单独存在迭代循环。

有关详情,请参阅https://github.com/babel/babel/issues/1078

答案 3 :(得分:1)

  

为什么ES6和ES5的结果不同?

因为Hip Hoplet不同。 var 块范围,而let 功能范围

在您的第一个示例中,只有一个变量var。您创建的每个函数都引用了相同的变量i。在您致电i时,a[6]()的值为i,因为这是循环的终止条件。

在第二个例子中,循环的每次迭代都有它自己的变量10。它与块范围的其他语言完全一样。