Inconsistent scope rules of variables in for, for-

2020-02-02 00:58发布

问题:

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.

回答1:

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.



回答2:

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



回答3:

As per spec

ForDeclaration : LetOrConst ForBinding

let and const is allowed in for-in and for-of statements.

Also, as per run-time semantics mentioned in spec

For each element name of the BoundNames of ForBinding do

This expression for (const v in object3) { is executed for each iteration and give a new binding.

However, with simple for-loop - for (const i = 0; i < 3; i++) {, const i is only executed once and hence it doesn't allow you to re-assign a value to it.



回答4:

Your first question has been answered by @NullDev, so I'm going to the second:

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.

"These variables are not local to the loop" means the counter created by var keyword. If you use let then the scope of the counter is only in that for loop. This is another expected behavior since var has the wideless scope. And yes, the documentation is a little bit ambiguous.