Циклы кажутся простыми, но ошибки в них встречаются очень часто. Обычно проблема в границах, неверном шаге или в том, что условие никогда не становится false. Ниже - самые распространенные случаи и способы их быстро заметить.
Бесконечный цикл
Бесконечный цикл - это цикл, который не заканчивается. Чаще всего причина одна: переменная, влияющая на условие, не меняется.
Забыли изменить счетчик
let i = 0;
// while (i < 3) {
// console.log(i);
// } // i не меняется - бесконечный цикл
Исправление:
let i = 0;
while (i < 3) {
console.log(i);
i += 1;
}
Неправильный шаг
Если условие и шаг “смотрят в разные стороны”, цикл тоже может стать бесконечным.
let i = 0;
// while (i < 3) {
// i -= 1;
// } // i уходит в минус, условие i < 3 всегда true
Исправление - согласовать условие и шаг:
let i = 0;
while (i < 3) {
i += 1;
}
continue до изменения счетчика
В while это особенно опасно: continue может перепрыгнуть строку, где меняется счетчик.
let i = 0;
// while (i < 5) {
// if (i === 2) continue;
// i += 1;
// } // бесконечный цикл при i === 2
Исправление - менять i до continue:
let i = 0;
while (i < 5) {
i += 1;
if (i === 3) continue;
console.log(i);
}
Ошибки границ (границы цикла)
i <= arr.length вместо i < arr.length
При переборе массива индексы идут от 0 до length - 1. Если поставить <=, появится лишняя итерация с undefined.
const items = ["a", "b", "c"];
for (let i = 0; i <= items.length; i += 1) {
console.log(items[i]);
}
// "a", "b", "c", undefined
Правильно:
const items = ["a", "b", "c"];
for (let i = 0; i < items.length; i += 1) {
console.log(items[i]);
}
Начали не с того индекса
Если нужен весь массив, старт почти всегда 0. Иногда цикл начинается с 1 и первый элемент пропускается.
const items = ["a", "b", "c"];
for (let i = 1; i < items.length; i += 1) {
console.log(items[i]);
}
// "b", "c" (элемент "a" пропущен)
Off-by-one: ошибка на один шаг
Off-by-one - это ситуация, когда цикл делает на одну итерацию больше или меньше, чем нужно. Чаще всего это из-за < и <=, или из-за неверной стартовой/конечной точки.
Пример: нужно вывести числа 1, 2, 3, 4, 5.
Неверно:
for (let i = 1; i < 5; i += 1) {
console.log(i);
}
// 1 2 3 4
Правильно:
for (let i = 1; i <= 5; i += 1) {
console.log(i);
}
// 1 2 3 4 5
Изменение массива во время перебора
Если в цикле удаляются элементы из массива, индексы сдвигаются. В результате некоторые элементы могут быть пропущены.
Пример проблемы (удаление с splice во время прохода вперед):
const nums = [1, 2, 2, 3, 2];
for (let i = 0; i < nums.length; i += 1) {
if (nums[i] === 2) {
nums.splice(i, 1);
}
}
console.log(nums); // может остаться [1, 2, 3] - один "2" пропущен
Более надежный подход - идти с конца:
const nums = [1, 2, 2, 3, 2];
for (let i = nums.length - 1; i >= 0; i -= 1) {
if (nums[i] === 2) {
nums.splice(i, 1);
}
}
console.log(nums); // [1, 3]
Или не изменять массив, а создавать новый (часто так делают методами массивов).
Проблемы с for…of и изменением значений
for...of дает значение элемента, а не “ячейку массива”. Поэтому попытка изменить переменную цикла не изменит массив.
const nums = [1, 2, 3];
for (let n of nums) {
n = n * 2;
}
console.log(nums); // [1, 2, 3]
Если нужно менять массив по месту, обычно используют for по индексам:
const nums = [1, 2, 3];
for (let i = 0; i < nums.length; i += 1) {
nums[i] = nums[i] * 2;
}
console.log(nums); // [2, 4, 6]
Неправильное условие остановки
Иногда условие написано так, что цикл останавливается слишком рано или слишком поздно.
let i = 0;
while (i !== 3) {
i += 2;
// i будет 0, 2, 4, 6... и никогда не станет ровно 3
if (i > 100) break;
}
В таких случаях обычно нужно переписать условие, чтобы оно зависело от диапазона:
let i = 0;
while (i < 3) {
i += 2;
}
Что стоит запомнить
- Бесконечный цикл почти всегда связан с тем, что условие не меняется или шаг неверный.
- Для массива почти всегда используется
i < arr.length. - Off-by-one чаще всего появляется из-за
<и<=или неверной стартовой точки. - Если массив меняется внутри цикла, безопаснее идти с конца или не менять массив на месте.
- В
whileнужно особенно внимательно использоватьcontinue, чтобы не пропустить изменение счетчика.