Почему код работает при использовании цикла for...of, но перестаёт работать при использовании другого вида цикла (например for, for...in или forEach)?

<pre><code class="html">&lt;!doctype html&gt;
  • 15 апреля 2026 г. 9:01
Ответы на вопрос 3
Коротко: разные циклы работают по-разному — они отличаются тем, что именно и как перечисляют, как ведут себя с замыканиями/скоупом переменных, поддерживают ли await/break, и как реагируют на «живые» коллекции DOM. Из‑за этих отличий код на for...of может работать, а тот же код на for / for...in / forEach — ломаться. Ниже — наиболее частые причины и как их фиксить.

1) for...of перебирает значения итератора (iterable) — обычно то, что вам нужно
- для (const el of document.querySelectorAll('.btn')) { ... } — el — элемент.

2) for...in перебирает ключи (именованные свойства) — не для массивов/NodeList
- for (const k in arr) { /* k — строковый индекс или имя свойства */ }
- Поэтому он может вернуть свойства прототипа или строковые индексы, а не сами элементы — часто неожиданный результат.

3) Отличия в области видимости переменной (var vs let)
- Обычный for с var:
  for (var i = 0; i < buttons.length; i++) {
    buttons[i].addEventListener('click', () => console.log(i));
  }
  — при клике вы получите одно и то же (последнее) i, потому что var — одна переменная.
- Решения: использовать let (оно создаёт новую i для каждой итерации) или for...of / for...in с let:
  for (let i = 0; i < buttons.length; i++) { ... }
  или
  for (const btn of buttons) { ... }

4) forEach не поддерживает break/continue и плохо сочетается с async/await
- Нельзя выйти из forEach через break; для этого используйте for / for...of.
- Асинхронность:
  items.forEach(async item => { await doAsync(item); });
  — вызовы запускаются параллельно и внешний код не ждёт завершения всех await.
  Если нужно последовательно ждать:
  for (const item of items) {
    await doAsync(item);
  }

5) «Живые» коллекции DOM (getElementsByClassName / children) vs статичные NodeList
- getElementsByClassName возвращает HTMLCollection (живую): удаление/добавление элементов во время итерации изменит коллекцию и может сломать индексный for.
- querySelectorAll возвращает статичный NodeList (не меняется).
- При удалении элементов безопаснее итерация в обратном порядке:
  for (let i = elems.length - 1; i >= 0; i--) { /* удаляем elems[i] */ }

6) this и контекст в колбэках
- В forEach колбэк имеет собственный this (или undefined в строгом режиме). Arrow-функции сохраняют внешний this.
- for...of и обычный for не вводят дополнительных this.

Примеры типичных проблем и исправлений

a) Проблема с замыканием (var):
- Плохо:
  for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 0); // 3,3,3
  }
- Исправление:
  for (let i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 0); // 0,1,2
  }

b) Нужен await по очереди:
- Плохо:
  arr.forEach(async x => { await doX(x); }); // не ждёт последовательности
- Хорошо:
  for (const x of arr) {
    await doX(x);
  }

c) Попытка прервать forEach:
- Нельзя сделать break в forEach; использовать for/for...of:
  for (const x of arr) {
    if (cond(x)) break;
  }

Если пришлёте конкретный фрагмент кода, который «работает на for...of, но не работает на другом цикле», я точно покажу, из‑за чего и как поправить.
Для чего вообще цикл нужен чтобы определить минимальное и максимальное число? <br/> <br/> <pre><code class="javascript">Math.min(...[12, 5, 8, 20, 3, 16]); // 3
Math.max(...[12, 5, 8, 20, 3, 16]); // 20</code></pre>
в обычном for-е <code>i++</code> — это переприсваивание, <code>const</code> его запрещает. Первая итерация проходит (12 < 12 = false), потом update-шаг кидает TypeError и функция падает — minimum так и остаётся 12. <br/> <br/> <code>for...of</code> создаёт новый binding на каждой итерации, поэтому там <code>const</code> нормально работает.
Похожие вопросы