ECMAScript 2015:for循环中的const

99
以下两个代码片段(或者两个都不是/两个都可以)在一个完整的 ECMAScript 2015 实现中应该哪个能够运行:
for (const e of a)

for (const i = 0; i < a.length; i += 1)

据我理解,第一个例子应该可以工作,因为 e 在每次迭代时都会被初始化。那么对于第二个版本,i 是否也是这种情况?
我感到困惑,因为现有的实现(Babel,IE,Firefox,Chrome,ESLint)似乎并不一致,并且对于这两个循环变量有着不同的行为;同时我也无法找到标准中的具体说明,如果有的话,请提供,谢谢!

1
const 是用于常量的关键字,x.x 是常量,你应该使用 let。 - Patsy Issa
3
我的问题只是想澄清行为应该是什么样子的。例如,ESLint认为第一个示例是可以接受的(并且在prefer-const选项下更受欢迎),而第二个示例无效。大多数浏览器实现都认为这两个示例都无效。 - adrianp
4
据我所知,第一个在每次迭代时都重新初始化,因此它是可以正常工作的。它在Chrome中可以使用。 - lyschoening
@lyschoening 这也不应该适用于第二个例子吗? - adrianp
4
@adrianp 显然不是第二个例子。普通的 for 循环本质上等同于 {const i = 0; while(i < a.length) { /* for body */ i += 1}} - lyschoening
@lyschoening 那很有道理。 - adrianp
3个回答

110

3
为什么你链接到草案而不是最终规范?http://www.ecma-international.org/ecma-262/6.0/index.html。此外,你引用了`for`循环的错误评估规则。 const...不是一个表达式。你需要查看for(LexicalDeclaration Expression;Expression) Statement的规则。 - Felix Kling
4
没错,但我认为线索在步骤9。const在每次迭代中不会被重新声明。 - Felix Kling
4
在最新版的Firefox中,for (const e of a)似乎无法工作。我收到了SyntaxError: invalid for/in left-hand side的错误提示。 - Chris_F
1
这看起来有些违反直觉,因为规范说你只能声明一次 const ...?所以说,先声明 const a = 4,然后再声明 const a = 6 是可以的吗?这根本不是常量! - Kokodoko
2
MDN文档还说:“如果您不在块内重新分配变量,则也可以使用const代替let。” https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of#Examples - Phil Gibbins
显示剩余3条评论

62

这次我不会引用规范,因为我认为通过示例更容易理解发生了什么。

for (const e of a) …

基本上等同于

{
    const __it = a[Symbol.iterator]();
    let __res;
    while ((__res = __it.next()) && !__res.done) {
        const e = __res.value;
        …
    }
}

为简便起见,我忽略了表达式a中的e可能存在的TDZ问题以及在循环提前退出(例如breakthrow)时,在体内调用的各种__it.return()/__it.throw(e)方法。

for (const i = 0; i < a.length; i += 1) …

基本上等同于

{
    const i = 0;
    while (i < a.length) {
        …
        i += 1;
    }
}

let不同的是,在for循环中,const声明不会在每个循环迭代中重新声明(且初始化程序无论如何都不会被重新执行)。除非你在第一次迭代中break,否则你的i +=将会抛出异常。

但是,当你不重新分配变量时,for循环中的const仍然偶尔有用


对于我来说(节点5.0.0),for-of循环的工作方式并不如预期。在“let a = [1,3,5],b = []”和“for(const e of a){b.push(e)}”之后,我得到了“b == [1,1,1]”。这是节点的错误还是预期行为?根据您的代码,我期望每次迭代都会有一个新的“const e = __res.value”分配。 - Jürgen Strobel
2
@JürgenStrobel:一个旧的Node bug,其中const具有类似于var的作用域,并且在赋值时不会抛出异常。请使用严格模式。 - Bergi

6

你的第二个例子肯定不会起作用,因为i只声明了一次,而不是在每次迭代中声明,这只是该类循环工作方式的一个函数。

你可以在常规浏览器中尝试这个:

for (var i = 0, otherVar = ""; i < [1,2,3,4].length; i += 1){
  console.log(otherVar)
  otherVar = "If otherVar was initialized on each iteration, then you would never read me.";
}

并不是说在 for 循环中完全不允许使用 const。只有会修改 constfor 循环是不被允许的。

以下是有效的示例:

for(const i = 0;;){ break } 
for(const i = 0; i < 10;){ break; } 

以下是无效的:

for(const i = 0;;){ ++i; break; } 
for(const i = 0;;++i){ if(i > 0) break; }

我不确定为什么Firefox在阅读ES2015规范后会出现SyntaxError(尽管我相信Mozilla的聪明人是正确的),这似乎应该引发异常:
新建一个未初始化的不可变绑定,将字符串值N作为绑定名称的文本。如果S为true,则在初始化绑定之前尝试访问绑定的值或在初始化后设置它将始终抛出异常,无论引用该绑定的操作的严格模式设置如何。 S是一个可选参数,默认值为false。

1
那么你的意思是 const i 只声明一次,但 let i 不会?你的例子只展示了 var i 的工作原理,而不是 const i。由于 varconstlet 之间存在明显的区别,我认为引用关于 const 的规范将非常有价值。 - Felix Kling
1
@FelixKling,您误读了我的输入。我是说for循环中该部分的所有内容只声明一次。然后正交地,const值只能被赋值一次,任何尝试重新声明const都不起作用。我的示例是为了证明声明仅发生一次,提问者理解const的语义。let不是问题,而且我没有明确表示您所暗示的意思,您误读了。 - Kit Sunde
但是,如果“everything”只被声明一次,为什么在使用“let”时会得到每个迭代范围内的变量?还是我还没有理解您的意思? - Felix Kling
@FelixKling 您可以将let限制在迭代范围内并且禁止重新声明const。 在Firefox中运行此代码: function test(){console.log(“我将仅运行一次”);} for(let i = 1,_ = test();i <10; ++ i){console.log(i);} - Kit Sunde
3
在聊天中的评论:如果使用 let,那么每次迭代都会得到自己的 i 的副本。for(let foo = 0; i < 10; ++i){}等同于(funtion() { for(var i = 0; i < 10; ++i){ (function(i) { }(i)) } }());我的理解是:letconst 都是块级作用域。对于 constletfor/infor/of 暴露了相同的行为,但普通的 for 循环却没有。它在处理 const 时明显地表现不同(或许可以理解)。你简单地说它是“声明一次”,但我认为这样过于简化了。 - Felix Kling
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接