Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inconsistent scope rules of variables in for, for-in and for-of loops

So I noticed that I have to use let inside a for loop, and cannot use const. However, I found that I can use const inside the for-in and for-of constructs (code below). Intuitively I can rationalize that this is because the for loop is implemented differently/is more primitive, whereas the other constructs desugar into for loops where the iterating variable is assigned at the top of the for loop.

// Doesn't work
for (const i = 0; i < 3; i++) {
  console.log(i);
}

// Works
for (let i = 0; i < 3; i++) {
  console.log(i);
}

// Works
const object2 = ['a', 'b', 'c'];
for (const v of object2) {
  console.log(v);
}

// Works
const object3 = {
  a: 'a',
  b: 'b',
  c: 'c',
};
for (const v in object3) {
  console.log(v);
}

The only thing I could find on Mozilla MDN about this was on the for loop page:

This expression may optionally declare new variables with the var keyword. These variables are not local to the loop, i.e. they are in the same scope the for loop is in. The result of this expression is discarded.

Which also seems wrong, because if we use a let for i then i is no longer in scope after the for loop (which is consistent with other languages)

for (let i = 0; i < 3; i++) {
  console.log(i);
}
// Doesn't work as expected
console.log(i);

My question is whether this behaviour is expected and defined in the spec somewhere? MDN doesn't say much about this.

like image 261
dualscyther Avatar asked Nov 26 '25 12:11

dualscyther


2 Answers

Yes. This is indeed expected behavior.

const defines a variable which, as the name suggested, stays constant. That means the value of a const cannot change.

Now what you do in your for loop is incrementing "i", which was defined as a constant.

for (const i = 0; i < 3; i++ /* <- this doesn't work */ ) {
    console.log(i);
}

with for .. in or for .. of however, you just bind the variable.

In other words: With for .. in/off, the variable gets assigned once before execution of the loop and not on every iteration. Therefore const can indeed be used.

As for the reference:

ForDeclaration : LetOrConst ForBinding

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

like image 50
NullDev Avatar answered Nov 28 '25 02:11

NullDev


So I noticed that I have to use let inside a for loop, and cannot use const.

No. You can use a const declaration in a for loop just fine. The problem just is that const declares a constant binding, so an increment i++ doesn't work on const i (it should throw an exception, make sure you're in strict mode).

An example of how to use const:

for (const o = {index: 0, value: null}; o.index < arr.length; o.index++) {
    o.value = arr[o.index];
    doSomething(o);
}

Or one where it makes more sense:

for (const iterator = makeIterator(); !iterator.isDone(); iterator.next())
    doSomething(iterator.getCurrent());
}

Intuitively I can rationalize that this is because the for loop is implemented differently/is more primitive, whereas the other constructs desugar into for loops where the iterating variable is assigned at the top of the for loop.

Yes. In a for loop, you need to take care of updating the iteration variables yourself.

  • for ([var] init; condition; update) {
        body
    }
    becomes
    [var] init;
    while (condition) {
        body;
        update;
    }
  • for (const init; condition; update) {
        body
    }

    becomes

    {
        const init;
        while (condition) {
            body;
            update;
        }
    }
  • for (let init; condition; update) {
        body
    }

    becomes something more complicated

In for … in and for … of loops, you just declare an assignment target expression for the produced value.

  • for ([var]/let/const target of iterable) {
        body
    }

    becomes

    {
        const _iterator = iterable[Symbol.iterator]();
        let _result;
        while (!(_result = _iterator.next()).done) {
            [var]/let/const target = _result.value;
            body;
        }
    }
  • for (… in enumerable) is just the same as for (… of Reflect.enumerate(enumerable)).

The only thing I could find on Mozilla MDN about this was on the for loop page, which also seems wrong.

Yes, looks like that section hasn't yet been updated for ES6.

like image 28
Bergi Avatar answered Nov 28 '25 01:11

Bergi



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!