ECMAScript 2015:for循环中的const


83

以下两个(或都不/两个)代码片段中的哪个应在完整的ECMAScript 2015实现中工作:

for (const e of a)

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

据我了解,第一个示例应该有效,因为它e是为每次迭代初始化的。i在第二个版本中也不应该这样吗?

我很困惑,因为现有的实现(Babel,IE,Firefox,Chrome,ESLint)似乎不一致,并且具有的完整实现,并且具有const两个循环变体的各种行为。我也无法在标准中找到具体点,因此将不胜感激。


1
const用于常量xx,您应该改用let
Patsy Issa 2015年

3
@JamesThorpe不,我的问题只是试图弄清楚行为应该是什么。例如,ESLint认为第一个示例“确定”(并且首选带prefer-const选项),第二个示例无效。大多数浏览器实现都认为这两个示例均无效。
adrianp

4
AFAIK第一个可以,因为每次迭代都将其重新初始化。它适用于Chrome。
lyschoening

@lyschoening,这也不适用于第二个示例吗?
adrianp

4
@adrianp绝对不是第二个例子。常规的for循环本质上等效于{const i = 0; while(i < a.length) { /* for body */ i += 1}}
yschoening

Answers:


91

以下for-of循环起作用:

for (const e of a)

ES6规范将其描述为:

ForDeclaration:LetOrConst ForBinding

http://www.ecma-international.org/ecma-262/6.0/index.html#sec-for-in-and-for-of-statements-static-semantics-boundnames

强制性的for循环将不起作用:

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

这是因为声明仅在执行循环主体之前被评估一次。

http://www.ecma-international.org/ecma-262/6.0/index.html#sec-for-statement-runtime-semantics-labelle贬值


3
为什么您链接到草案而不是最终规格?ecma-international.org/ecma-262/6.0/index.html。另外,您还引用了错误的for循环评估规则。const ...不是表达。您需要查看的规则for ( LexicalDeclaration Expression ; Expression) Statement
Felix Kling 2015年

@FelixKling仍将旧链接添加为书签。您对评估规则是正确的。结论应该仍然是相同的,因为一个不变的绑定仅创建了一次(在步骤5中)?
lyschoening

4
是的,但是我认为线索在步骤9中const。每次迭代都不会重新声明s。
Felix Kling 2015年

4
for (const e of a)在最新版本的Firefox中似乎不起作用。我得到SyntaxError: invalid for/in left-hand side
Chris_F '16

2
MDN文档还说:“如果不在块内重新分配变量,则可以使用const而不是let。” developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
菲尔·吉宾斯

44

这次我不会引用规范,因为我认为通过示例来理解会更容易。

for (const e of a) …

基本上相当于

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

为简单起见,我忽略e了该a表达式的TDZ ,以及在循环过早退出(或在体内)的情况下的各种__it.return()/__it.throw(e)调用。breakthrow

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

基本上相当于

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

与此相反let,一个const在声明for循环没有得到在每一个循环迭代重新声明(和初始化器是无论如何不能再执行)。除非您break是第一次迭代,否则您i +=将抛出此处。


对于我(节点5.0.0)而言,for-of循环未按预期工作。在'let a = [1,3,5],b = []'和'for(a的常量e){b.push(e)}'之后,我得到'b == [1,1,1]' 。节点错误或预期行为?根据您的代码,我希望每次迭代都有一个新的'const e = __res.value'赋值。
尔根·斯特罗贝尔

2
@JürgenStrobel:一个老的节点错误,const具有类似var范围的作用,并且不会抛出任务。使用严格模式。
Bergi

我知道这是旧的,但需要稍作修正:while ( ( __res = __it.next() ) && !__res.done) {。否则__res最终成为truefalse
JDB仍然记得Monica

@JDB谢谢,固定!顺便说一句,如果您刚刚编辑它,我会很好的。
Bergi

3

您的第二个示例绝对不应该工作,因为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.";
}

循环中const完全不允许的情况并非如此for。只有for这样才能修改const。

这些是有效的:

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。


在不引用来源的情况下怎么说呢?为什么会这样const而不是let呢?
Felix Kling 2015年

@FelixKling您认为哪条陈述需要引用?const不允许自己重新分配(这是没有争议的),并且正如我的示例清楚地说明for了变量定义部分如何工作,与他的隐含期望相反。的值let可以更改,在功能上与给var定示例中的功能相同,但这let不是问题的一部分。
Kit Sunde

1
因此,您明确表示const i仅声明一次,但let i不会声明一次吗?您的示例仅演示了var i工作原理,而不是const i。由于有一个明显的区别varconst而且let,我觉得他援引相对于符合规范const将是非常有价值的。
Felix Kling 2015年

1
@FelixKling您在误读我输入的内容。我说的是for循环的该部分中的所有内容都声明一次。然后,正交const值只能分配一次,任何重新声明的尝试都const将不起作用。我的示例旨在证明以下事实:声明仅发生一次,询问问题的人理解的语义constlet这不是问题,不,我没有明确表示您的建议是您误读了。
Kit Sunde

3
在即时通讯中评论:如果您使用let,则每次迭代都会获得其自己的副本ifor(let foo = 0; i < 10; ++i){} 相当于 (funtion() { for(var i = 0; i < 10; ++i){ (function(i) { }(i)) } }()); 这就是我来自哪里:letconst都在块范围内。for/infor/of公开相同的行为constlet,但正常的for循环没有。它显式地const区别对待(也许可以理解)。您只是说它被“声明一次”,但是简化了IMO。
菲利克斯·克林
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.