ECMAScript 2015: константа в циклах for

84

Какой из двух (или ни одного / обоих) фрагментов кода ниже должен работать в полной реализации 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
Пэтси Исса
3
@JamesThorpe Нет, мой вопрос просто пытается прояснить, каким должно быть поведение. Например, ESLint считает первый пример нормальным (и предпочитает с prefer-constопцией), а второй - недействительным. Большинство реализаций браузеров считают оба примера недопустимыми.
Адрианп,
4
AFAIK, первый в порядке, так как он повторно инициализируется с каждой итерацией. Работает в Chrome.
lyschoening
@lyschoening, и это не должно относиться и ко второму примеру?
Адрианп
4
@adrianp точно не второй пример. Обычный цикл for по сути эквивалентен to{const i = 0; while(i < a.length) { /* for body */ i += 1}}
lyschoening

Ответы:

92

Следующий цикл 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-labelledevaluation

Lyschoening
источник
3
Почему вы ссылаетесь на черновик, а не на окончательную спецификацию? ecma-international.org/ecma-262/6.0/index.html . Также вы ссылаетесь на неправильное правило оценки forцикла. const ...это не выражение. Вам нужно посмотреть на правило для for ( LexicalDeclaration Expression ; Expression) Statement .
Феликс Клинг,
У @FelixKling старая ссылка все еще была добавлена ​​в закладки. Вы правы насчет правила оценки. Вывод должен быть таким же, потому что неизменяемая привязка создается ровно один раз (на шаге 5)?
lyschoening
4
Верно, но я думаю, что ключ кроется в шаге 9. consts не объявляются повторно для каждой итерации.
Феликс Клинг,
4
for (const e of a)НЕ работает в последней версии Firefox. Я получаюSyntaxError: invalid for/in left-hand side
Chris_F
2
В документации MDN также говорится: «Вы также можете использовать const вместо let, если не переназначаете переменную внутри блока». developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Фил Гиббинс
45

На этот раз я не буду цитировать спецификацию, потому что думаю, что легче понять, что происходит на примере.

for (const e of a) …

В основном эквивалентен

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

Для простоты я проигнорировал наличие TDZ с eдля aвыражения и различных вызовов __it.return()/ __it.throw(e)в случае преждевременного выхода из цикла ( breakили throwв теле).

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 (const e of a) {b.push (e)} »я получаю' b == [1, 1, 1] ' . Ошибка узла или предполагаемое поведение? В соответствии с вашим кодом я ожидал бы нового присвоения 'const e = __res.value' на итерацию.
Jürgen Strobel
2
@ JürgenStrobel: старая ошибка узла, в которой constимеет var-подобную область видимости и не передает назначение. Используйте строгий режим.
Берги
Я знаю , что это старое, но небольшая коррекция: while ( ( __res = __it.next() ) && !__res.done) {. В противном случае в __resконечном итоге будет trueилиfalse
JDB все еще помнит Монику
@JDB Спасибо, исправлено! Между прочим, я был бы в порядке, если бы вы его только что отредактировали.
Берги
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 выдает SyntaxError после прочтения спецификации ES2015 (хотя я уверен, что умники из Mozilla правы), похоже, что он должен вызвать исключение:

Создайте новую, но неинициализированную неизменяемую привязку в записи среды. Строковое значение N - это текст связанного имени. Если S имеет значение true, то попытки получить доступ к значению привязки до ее инициализации или установить ее после инициализации всегда будут вызывать исключение, независимо от настройки строгого режима операций, которые ссылаются на эту привязку. S - необязательный параметр, значение по умолчанию - false.

Кит Сунде
источник
Как можно так сказать, не ссылаясь на источник? Почему это так, constа не для let?
Феликс Клинг,
@FelixKling Какое утверждение, по вашему мнению, требует цитирования? constне позволяет переназначить себя (это не оспаривается), и, как мой пример, ясно демонстрирует, как forработает для части определения переменной вопреки его подразумеваемым ожиданиям. Значение letможно изменить, оно функционально эквивалентно приведенному varв примере, но letне является частью вопроса.
Kit Sunde
1
Итак, вы явно имеете в виду, что const iэто объявляется только один раз, но let iнет? Ваш пример только демонстрирует, как var iработает, а не const i. Поскольку существует четкая разница между var, constи let, я думаю, что ссылка на его спецификацию в отношении них была constбы очень ценной.
Феликс Клинг,
1
@FelixKling Вы неправильно понимаете то, что я печатаю. Я говорю, что все в этом разделе цикла for объявляется один раз. Тогда ортогональное constзначение может быть присвоено только один раз, любая попытка повторного объявления constне будет работать. Мой пример - продемонстрировать тот факт, что объявление происходит только один раз, и человек, задающий вопросы, понимает семантику const. letне проблема, и нет, я явно не имел в виду то, что вы предлагаете, вы неправильно прочитали.
Кит Сунд,
3
Прокомментировал в чате: Если используете let, то на каждой итерации будет свой экземпляр i. for(let foo = 0; i < 10; ++i){} эквивалентно (funtion() { for(var i = 0; i < 10; ++i){ (function(i) { }(i)) } }()); This is where I am from: letи constоба имеют блочную область видимости. for/inи for/ofвыставляем такое же поведение для constи let, но в обычном forцикле этого не происходит. Он явно по-const разному относится (возможно, понятно). Вы просто говорите, что он «объявляется один раз», но это слишком упрощает ИМО.
Феликс Клинг,