Пошук уроків, статей та іншого контенту
Навіщо потрібні цикли, класичний for, for...of, for...in, while, do...while, мітки та керування ітерацією.
Без циклів довелося б писати однакову дію окремим рядком коду для кожного елемента: console.log(arr[0]), потім console.log(arr[1]) і так далі — незручно, а для масиву невідомого розміру взагалі неможливо. Цикл дозволяє виконати один і той самий блок коду багато разів, змінюючи лише те, з якими даними він працює на кожному кроці.
Заголовок for складається з трьох частин, розділених крапкою з комою: ініціалізація (виконується один раз, перед першою ітерацією), умова (перевіряється перед кожною ітерацією — цикл продовжується, поки вона truthy) і крок (виконується після кожної ітерації):
for (let i = 0; i < 5; i++) {
// ^ ініціалізація ^ умова ^ крок
console.log(i); // 0, 1, 2, 3, 4
}Усі три частини необов'язкові й незалежні одна від одної — це дає гнучкість, якою рідко користуються новачки:
// Зворотний відлік
for (let i = 5; i > 0; i--) {
console.log(i); // 5, 4, 3, 2, 1
}
// Крок 2 замість 1
for (let i = 0; i < 10; i += 2) {
console.log(i); // 0, 2, 4, 6, 8
}Найзручніший спосіб пройтись по масиву, рядку, Map чи Set, коли потрібні саме значення, а не індекси. Він працює з будь-яким ітерованим (iterable) об'єктом — тим, що реалізує протокол ітерації, докладніше про який поговоримо в модулі «Advanced JavaScript»:
const fruits = ["яблуко", "банан", "вишня"];
for (const fruit of fruits) {
console.log(fruit);
}
// Працює і для рядків — посимвольно
for (const char of "Привіт") {
console.log(char);
}Якщо потрібен ще й індекс, скористайтеся entries() разом із деструктуризацією (детально про деструктуризацію — у наступному модулі):
for (const [index, fruit] of fruits.entries()) {
console.log(index, fruit); // 0 "яблуко", 1 "банан", 2 "вишня"
}const user = { name: "Ігор", age: 28 };
for (const key in user) {
console.log(key, user[key]);
}Головна різниця між двома конструкціями:
for...of перебирає значення й призначений для ітерованих колекцій (масиви, рядки, Map, Set).
for...in перебирає ключі (імена властивостей) і призначений для звичайних об'єктів.
for...in для масивів — поширена помилка: він перебирає індекси як рядки, не гарантує порядок і може захопити успадковані властивості. Для масивів завжди використовуйте for...of або методи масивів (map, forEach) — про них у наступному модулі.
for найкраще підходить, коли ви наперед знаєте кількість ітерацій. Коли умова завершення залежить від того, що станеться під час виконання (наприклад, читання даних, поки вони не закінчаться), природніше використати while:
let attempts = 0;
while (attempts < 3) {
attempts++;
}У while умова перевіряється до першої ітерації — якщо вона одразу false, тіло циклу не виконається жодного разу. do...while перевіряє умову після тіла, тому воно завжди виконується принаймні раз — корисно, коли потрібно щось зробити перед першою перевіркою (наприклад, показати меню перед очікуванням вводу):
let input;
do {
input = promptUser();
} while (!input);break повністю зупиняє цикл, continue пропускає лише поточну ітерацію й переходить до наступної:
for (const num of [1, 2, 3, 4, 5]) {
if (num === 3) continue; // пропустити цю ітерацію
if (num === 5) break; // повністю зупинити цикл
console.log(num); // 1, 2, 4
}break і continue за замовчуванням діють лише на найближчий цикл, що їх оточує. Мітка (label) дозволяє перервати одразу зовнішній цикл із середини вкладеного:
outer: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (i === 1 && j === 1) break outer; // зупиняє ОБИДВА цикли
console.log(i, j);
}
}Мітки — рідкісний інструмент. У переважній більшості реального коду достатньо звичайного break; винесіть внутрішній цикл в окрему функцію з return, якщо логіка стає заплутаною — це часто читається краще за мітки.
Це критично для замикань усередині циклу — детально розглянемо в модулі про функції, але ключова відмінність від var варта згадки вже тут:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // 3, 3, 3 — усі бачать фінальне значення
}
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 0); // 0, 1, 2 — кожна ітерація має свою j
}Типова задача, де break економить реальну роботу — якомога швидше зупинити перебір, щойно знайдено потрібне:
function findFirstAdmin(users) {
for (const user of users) {
if (user.role === "admin") {
return user; // не потрібно перевіряти решту користувачів
}
}
return null;
}У наступному модулі побачимо, що для типових задач (знайти, відфільтрувати, перетворити колекцію) методи масивів — find, filter, map — зазвичай читаються ще чистіше за ручний цикл. Цикли лишаються незамінними, коли потрібне складне керування ітерацією (кілька змінних, дострокова зупинка з кількох умов, зворотний перебір).
for...in для масивів замість for...of.
Нескінченний цикл через забуту зміну умови (наприклад, забутий i++ або зміна умови всередині тіла, яка ніколи не настає).
Використання var у циклі з асинхронним кодом усередині — усі колбеки бачать останнє значення змінної.
Модифікація масиву (наприклад, splice) під час перебору через for...of — це може пропустити елементи або спричинити непередбачувану поведінку.
for — коли кількість ітерацій відома заздалегідь або потрібен повний контроль (крок, напрямок). for...of — за замовчуванням для перебору значень масивів, рядків та інших колекцій. for...in — лише для ключів звичайних об'єктів. while/do...while — коли умова завершення залежить від того, що станеться під час виконання. break і continue керують потоком усередині циклу, а мітки дозволяють це зробити для вкладених циклів одразу.
Спробуйте самостійно
Напишіть цикл, який виводить лише парні числа від 0 до 20, використовуючи continue для непарних. Потім напишіть другу версію тієї самої задачі без continue, змінивши крок циклу.