99

Which of the two (or neither/ both) code fragments below should be working in a complete ECMAScript 2015 implementation:

for (const e of a)

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

From my understanding, the first example should work because e is initialized for each iteration. Shouldn't this also be the case for i in the second version?

I'm confused because existing implementations (Babel, IE, Firefox, Chrome, ESLint) do not seem to be consistent and have a complete implementation of const, with various behaviours of the two loop variants; I'm also not able to find a concrete point in the standard, so that would be much appreciated.

adrianp
  • 2,491
  • 5
  • 26
  • 44
  • 1
    const is for constants x.x, you should use let instead – Patsy Issa Aug 13 '15 at 11:54
  • 3
    @JamesThorpe No, my question simply tries to clear out what the behaviour should be. For example, ESLint considers the first example OK (and prefered with `prefer-const` option) and second one invalid. Most browser implementations consider both examples invalid. – adrianp Aug 13 '15 at 11:56
  • 4
    AFAIK the first one is OK as it is re-initialized with each iteration. It works in Chrome. – lyschoening Aug 13 '15 at 11:59
  • @lyschoening and this shouldn't apply for the second example also? – adrianp Aug 13 '15 at 12:01
  • 4
    @adrianp definitely not the second example. The regular for loop is essentially equivalent to to `{const i = 0; while(i < a.length) { /* for body */ i += 1}}` – lyschoening Aug 13 '15 at 12:05
  • @lyschoening that makes sense – adrianp Aug 13 '15 at 12:08

3 Answers3

110

The following for-of loop works:

for (const e of a)

The ES6 specification describes this as:

ForDeclaration : LetOrConst ForBinding

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

The imperative for loop will not work:

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

This is because the declaration is only evaluated once before the loop body is executed.

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

lyschoening
  • 18,170
  • 11
  • 44
  • 54
  • 3
    Why do you link to the draft and not the final spec? http://www.ecma-international.org/ecma-262/6.0/index.html. Also you are citing the wrong evaluation rule for the `for` loop. `const ...` is not an expression. You need to look at the rule for `for ( LexicalDeclaration Expression ; Expression) Statement `. – Felix Kling Aug 13 '15 at 13:44
  • @FelixKling still had the old link bookmarked. You're right about the evaluation rule. The conclusion should still be the same because an immutable binding is created exactly once (in step 5)? – lyschoening Aug 13 '15 at 14:02
  • 4
    Right, but I think the clue is in step 9. `const`s are not redeclared per iteration. – Felix Kling Aug 13 '15 at 14:05
  • 4
    `for (const e of a)` does NOT appear to work in the latest version of Firefox. I'm getting `SyntaxError: invalid for/in left-hand side` – Chris_F Apr 21 '16 at 06:42
  • 1
    It looks counter-intuitive because the spec says you can only declare a `const` once...? So it's ok to say `const a = 4` and then later say `const a = 6` ? That doesn't seem constant at all! – Kokodoko Oct 05 '17 at 13:27
  • `for (const e of a)` works in Firefox 51+ \[[`for...of` browser compatibility table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of#Browser_compatibility)] – myrdd Oct 18 '17 at 08:02
  • 2
    MDN docs also say "You can use const instead of let too, if you don't reassign the variable inside the block." https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of#Examples – Phil Gibbins Dec 10 '18 at 09:39
  • http://www.ecma-international.org/ecma-262/6.0/index.html#sec-for-statement-runtime-semantics-labelledevaluation – cRAN Jan 05 '20 at 08:50
62

I won't cite the spec this time, because I think it's easier to understand what happens by example.

for (const e of a) …

Is basically equivalent to

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

For simplicity I've ignored that there's a TDZ with e for the a expression, and the various __it.return()/__it.throw(e) calls in the case the loop exits prematurely (break or throw in the body).

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

is basically equivalent to

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

In contrast to let, a const declaration in a for loop does not get redeclared in every loop iteration (and the initialiser is not re-executed anyway). Unless you break in the first iteration, your i += will throw here.

However, const in for loops is still occasionally useful when you don't reassign the variable.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • For me (node 5.0.0) the for-of loop is not working as expected. After 'let a = [1,3,5], b = []' and 'for (const e of a) { b.push(e) }' I get 'b == [ 1, 1, 1 ]'. A node bug, or intended behavior? As per your code I'd expect a fresh 'const e = __res.value' assignment per iteration. – Jürgen Strobel Nov 24 '15 at 15:57
  • 2
    @JürgenStrobel: An old node bug where `const` has `var`-like scope and doesn't throw on assignment. Use strict mode. – Bergi Nov 24 '15 at 18:09
6

Your second example should definitely not work because i is declared once and not on each iteration this is just a function of how that category of loops work.

You can try this in a regular browser:

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.";
}

It's not the case that const is entirely disallowed in for loops. Only for that will modify const is.

These are valid:

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

These are invalid:

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

I'm not sure why Firefox gives a SyntaxError after reading the ES2015 spec (although I'm sure the clever folk at Mozilla are correct), it seems like it's supposed to raise an exception:

Create a new but uninitialized immutable binding in an Environment Record. The String value N is the text of the bound name. If S is true then attempts to access the value of the binding before it is initialized or set it after it has been initialized will always throw an exception, regardless of the strict mode setting of operations that reference that binding. S is an optional parameter that defaults to false.

Kit Sunde
  • 35,972
  • 25
  • 125
  • 179
  • How can you say that without citing the source? Why would that be the case for `const` but not for `let`? – Felix Kling Aug 13 '15 at 13:49
  • @FelixKling Which statement do you think needs citation? `const` doesn't allow itself to be re-assigned (this isn't contested) and as my example clearly demonstrates how `for` works for the variable definition portion contrary to his implied expectations. The value of `let` can be changed, it's functionally equivalent to `var` in the example given, but `let` isn't part of the question. – Kit Sunde Aug 13 '15 at 14:07
  • 1
    So you explicitly mean that `const i` is only declared once, but `let i` wouldn't? Your example only demonstrates how `var i` works, not `const i`. Since there is a clear difference between `var`, `const` and `let`, I think citing he spec with respect to `const` would be very valuable. – Felix Kling Aug 13 '15 at 14:10
  • 1
    @FelixKling You're misreading what I'm typing. I'm saying **everything** in that section of the for loop is declared once. Then orthogonally `const` value can only be assigned once, any attempt to redeclare `const` will not work. My example is to demonstrate the fact that the declaration only happens once, the person asking the questions understands the semantics of `const`. `let` isn't the issue, and no I didn't explicitly mean what you're suggesting, you misread. – Kit Sunde Aug 13 '15 at 14:19
  • But if "everything" is declared once, why do I get per iteration scoped variables if I use `let`? Or am I still misunderstanding you? – Felix Kling Aug 13 '15 at 14:22
  • @FelixKling You can have `let` be iteration scoped **and** disallow const being redeclared. Run this in firefox: `function test(){console.log("I'll be run once");} for(let i = 1, _ = test();i < 10;++i){console.log(i);}` – Kit Sunde Aug 13 '15 at 14:27
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/86901/discussion-between-kit-sunde-and-felix-kling). – Kit Sunde Aug 13 '15 at 14:33
  • 3
    Commented in the chat: If you use `let`, then each iteration gets its own copy of `i`. `for(let foo = 0; i < 10; ++i){}` is equivalent to `(funtion() { for(var i = 0; i < 10; ++i){ (function(i) { }(i)) } }());` This is where I am coming from: `let` and `const` are both block scoped. `for/in` and `for/of` expose the same behavior for `const` and `let`, but the normal `for` loop does not. It **explicitly** treats `const` differently (understandably maybe). You simply say that it is "declared once" but that simplifies it too much IMO. – Felix Kling Aug 13 '15 at 15:12
  • I don't see what is wrong with `for (const i = 0;;++i) { break }`. It's equivalent to `for (const i = 0;;) { break; ++i }`, where `i` is never mutated. – Bergi Aug 13 '15 at 23:02
  • @Bergi Right you are, good catch! I updated my example, although firefox disallows that version as well, presumably the parser isn't clever enough. – Kit Sunde Aug 14 '15 at 09:22