Пошук уроків, статей та іншого контенту
Навчіться обирати між for, while, do...while, for...of і for...in залежно від задачі та структури даних. Розгляньте практичні сценарії та критерії вибору найбільш читабельного рішення.
Правильний цикл — це не лише питання синтаксису. Насамперед потрібно визначити:
що саме перебираємо: числа, елементи масиву, властивості об’єкта;
чи відома кількість повторень заздалегідь;
чи потрібно гарантовано виконати тіло циклу хоча б один раз;
чи потрібен індекс елемента;
чи може ітерація завершитися достроково;
що буде найбільш зрозумілим для іншого розробника.
У JavaScript найчастіше використовують такі цикли:
for — коли потрібен лічильник, індекс або складне керування кроком;
while — коли кількість повторень наперед невідома;
do...while — коли тіло потрібно виконати хоча б один раз;
for...of — для перебору значень ітерованих структур;
for...in — для перебору іменованих властивостей об’єкта.
forЦикл for добре підходить, якщо:
відома або легко описується кількість повторень;
потрібен індекс;
потрібно змінювати крок;
потрібно проходити масив не від початку до кінця;
умова завершення залежить від кількох змінних.
for (початкове_значення; умова; крок) {
// тіло циклу
}Приклад:
const prices = [120, 80, 250, 90];
let total = 0;
for (let index = 0; index < prices.length; index += 1) {
total += prices[index];
}
console.log(total); // 540Тут for доречний, тому що програмі потрібні і позиція елемента, і послідовний рух за індексами.
const messages = ["Перше", "Друге", "Третє"];
for (let index = messages.length - 1; index >= 0; index -= 1) {
console.log(messages[index]);
}Результат:
Третє
Друге
Першеfor (let number = 0; number <= 10; number += 2) {
console.log(number);
}Цикл виведе парні числа від 0 до 10.
for стає менш читабельнимЯкщо індекс вам не потрібен, класичний for часто додає зайвий код:
const users = ["Олена", "Андрій", "Марія"];
for (let index = 0; index < users.length; index += 1) {
console.log(users[index]);
}У такому випадку краще використати for...of:
for (const user of users) {
console.log(user);
}whilewhile підходить, коли кількість ітерацій невідома заздалегідь, а цикл має працювати, поки виконується певна умова.
let attempts = 0;
while (attempts < 3) {
console.log(`Спроба ${attempts + 1}`);
attempts += 1;
}У цьому прикладі кількість ітерацій усе ще можна порахувати, тому технічно підійшов би й for. Але while добре показує логіку: виконувати дії, поки спроб менше трьох.
Практичніший приклад — пошук першого додатного числа:
const values = [-10, -4, -2, 7, 12];
let index = 0;
while (index < values.length && values[index] <= 0) {
index += 1;
}
if (index < values.length) {
console.log(`Перше додатне число: ${values[index]}`);
} else {
console.log("Додатних чисел немає");
}whileУ циклі while потрібно самостійно змінювати стан, від якого залежить умова. Якщо цього не зробити, виникне нескінченний цикл:
let counter = 0;
while (counter < 3) {
console.log(counter);
// Без цього рядка counter не змінюється,
// тому умова завжди залишається істинною.
counter += 1;
}Зазвичай змінювання лічильника розміщують у тілі циклу так, щоб воно гарантовано виконувалося на кожній ітерації.
do...whilewhileУ while умова перевіряється перед виконанням тіла. Якщо умова одразу хибна, тіло не виконається жодного разу.
let value = 10;
while (value < 5) {
console.log("Цей рядок не буде виведено");
}У do...while тіло виконується спочатку, а умова перевіряється після цього:
let value = 10;
do {
console.log("Цей рядок буде виведено один раз");
} while (value < 5);do...while доречний, якщо дія має відбутися хоча б один раз:
показ меню;
запит введення даних;
виконання операції з подальшою перевіркою результату;
повторення дії, доки користувач не введе коректне значення.
Приклад перевірки введення:
let password;
do {
password = prompt("Введіть пароль довжиною щонайменше 8 символів");
} while (password !== null && password.length < 8);
if (password === null) {
console.log("Введення скасовано");
} else {
console.log("Пароль прийнято");
}prompt доступний у браузері. У середовищі Node.js цей приклад без додаткового коду для введення не запуститься.
for...offor...of перебирає значення ітерованого об’єкта. Найчастіше його використовують із:
масивами;
рядками;
Set;
Map;
іншими ітерованими структурами.
const colors = ["синій", "жовтий", "зелений"];
for (const color of colors) {
console.log(color);
}Цей варіант читабельніший за класичний for, якщо індекс не потрібен.
Рядок також можна перебирати посимвольно:
const word = "JavaScript";
for (const character of word) {
console.log(character);
}for...of коректно працює з Unicode-кодпоінтами, тому зазвичай є кращим вибором для перебору символів, ніж доступ за числовим індексом.
Setconst uniqueTags = new Set(["js", "web", "js", "frontend"]);
for (const tag of uniqueTags) {
console.log(tag);
}Кожне значення буде виведено лише один раз, оскільки Set зберігає унікальні значення.
MapДля Map кожна ітерація повертає пару [key, value]:
const userRoles = new Map([
["Олена", "адміністратор"],
["Андрій", "редактор"],
]);
for (const [user, role] of userRoles) {
console.log(`${user}: ${role}`);
}Якщо потрібні і значення, і індекси, можна використати entries():
const tasks = ["аналіз", "розробка", "тестування"];
for (const [index, task] of tasks.entries()) {
console.log(`${index + 1}. ${task}`);
}for...infor...in перебирає імена властивостей об’єкта:
const product = {
name: "Клавіатура",
price: 2500,
inStock: true,
};
for (const property in product) {
console.log(`${property}: ${product[property]}`);
}Результатом будуть ключі name, price та inStock.
for...in не варто використовувати для масивівФормально for...in може перебирати індекси масиву, але це не найкращий вибір:
const numbers = [10, 20, 30];
for (const index in numbers) {
console.log(index); // "0", "1", "2" — це рядки, а не числа
}Проблеми такого підходу:
перебираються ключі, а не значення;
ключі мають рядковий тип;
можуть бути перебрані додаткові власні властивості;
порядок і поведінка не виражають намір так чітко, як for...of.
Для масиву використовуйте for...of:
for (const number of numbers) {
console.log(number);
}for...in може побачити властивості, успадковані через прототип. Якщо потрібно працювати лише з власними властивостями об’єкта, перевіряйте їх:
const settings = {
theme: "dark",
language: "uk",
};
for (const key in settings) {
if (Object.hasOwn(settings, key)) {
console.log(`${key}: ${settings[key]}`);
}
}Object.hasOwn(object, property) перевіряє, чи належить властивість безпосередньо цьому об’єкту.
break і continuebreakbreak негайно завершує цикл. Він корисний, коли потрібний результат уже знайдено:
const users = [
{ name: "Олена", active: false },
{ name: "Андрій", active: true },
{ name: "Марія", active: true },
];
let firstActiveUser = null;
for (const user of users) {
if (user.active) {
firstActiveUser = user;
break;
}
}
console.log(firstActiveUser);Після знаходження першого активного користувача перебирати решту масиву немає сенсу.
continuecontinue пропускає поточну ітерацію та переходить до наступної:
const values = [3, -1, 8, 0, 5];
for (const value of values) {
if (value <= 0) {
continue;
}
console.log(value);
}Будуть виведені лише додатні числа.
Не варто зловживати continue: якщо через нього з’являється багато вкладених умов, цикл може стати складним для читання.
Використовуйте for:
const items = ["A", "B", "C"];
for (let index = 0; index < items.length; index += 1) {
console.log(`Елемент ${index}: ${items[index]}`);
}Якщо потрібні тільки індекс і значення, зручним варіантом буде for...of з entries().
Використовуйте for...of:
for (const item of items) {
console.log(item);
}Використовуйте for...in, але пам’ятайте про власні властивості:
const scores = {
math: 95,
english: 88,
};
for (const subject in scores) {
if (Object.hasOwn(scores, subject)) {
console.log(`${subject}: ${scores[subject]}`);
}
}Також можна явно отримати ключі:
for (const subject of Object.keys(scores)) {
console.log(`${subject}: ${scores[subject]}`);
}Такий варіант часто легше комбінувати з іншими методами масивів.
Використовуйте while:
let number = 1;
while (number < 100) {
number *= 2;
}
console.log(number); // 128Використовуйте do...while:
let command;
do {
command = "exit";
console.log("Виконання команди");
} while (command !== "exit");У реальному коді значення command зазвичай надходить від користувача або іншого джерела даних.
Розглянемо кілька різних задач з одними даними:
const orders = [
{ id: 101, customer: "Олена", total: 1200, paid: true },
{ id: 102, customer: "Андрій", total: 850, paid: false },
{ id: 103, customer: "Марія", total: 2300, paid: true },
];
// for...of — потрібні самі замовлення.
let paidTotal = 0;
for (const order of orders) {
if (order.paid) {
paidTotal += order.total;
}
}
console.log(`Сума оплачених замовлень: ${paidTotal}`);
// for — потрібен порядковий номер замовлення.
for (let index = 0; index < orders.length; index += 1) {
const order = orders[index];
console.log(`${index + 1}. Замовлення №${order.id}`);
}
// break — потрібно знайти перше неоплачене замовлення.
let unpaidOrder = null;
for (const order of orders) {
if (!order.paid) {
unpaidOrder = order;
break;
}
}
console.log("Перше неоплачене замовлення:", unpaidOrder);Усі три цикли працюють із тим самим масивом, але кожен виражає іншу мету:
for...of — пройти всі значення;
for — контролювати індекс;
for з break — завершити пошук після першого збігу.
Цикли не завжди є найвиразнішим рішенням. Для простих операцій над масивами можуть бути доречні:
forEach() — виконати дію для кожного елемента;
map() — створити новий масив перетворених значень;
filter() — залишити елементи, що відповідають умові;
find() — знайти перший відповідний елемент;
some() — перевірити, чи є хоча б один відповідний елемент;
every() — перевірити, чи всі елементи відповідають умові;
reduce() — обчислити одне підсумкове значення.
Наприклад, замість ручного пошуку через for можна використати find():
const products = [
{ name: "Миша", available: false },
{ name: "Клавіатура", available: true },
{ name: "Монітор", available: true },
];
const availableProduct = products.find((product) => product.available);
console.log(availableProduct);Проте звичайний цикл може бути кращим, якщо:
потрібні break або continue;
операція має кілька послідовних кроків;
потрібно змінювати кілька зовнішніх змінних;
асинхронні дії мають виконуватися послідовно;
явний контроль потоку важливіший за компактність.
Для послідовного виконання асинхронних операцій зазвичай використовують for...of разом із await:
function wait(milliseconds) {
return new Promise((resolve) => {
setTimeout(resolve, milliseconds);
});
}
async function processItems(items) {
for (const item of items) {
await wait(100);
console.log(`Оброблено: ${item}`);
}
}
processItems(["A", "B", "C"]);У цьому прикладі наступний елемент обробляється після завершення попереднього.
Метод forEach() не очікує await так, як часто очікують початківці:
async function incorrect(items) {
items.forEach(async (item) => {
await wait(100);
console.log(item);
});
console.log("Цей рядок може виконатися раніше");
}Для послідовної асинхронної обробки обирайте for...of. Якщо операції незалежні та їх потрібно виконати паралельно, використовуйте Promise.all().
for...in для масивівНеправильно:
for (const index in numbers) {
console.log(numbers[index]);
}Краще:
for (const number of numbers) {
console.log(number);
}whileЯкщо умова залежить від змінної, ця змінна має змінюватися в тілі циклу:
let index = 0;
while (index < 5) {
console.log(index);
index += 1;
}Для масиву індекси починаються з 0, тому остання коректна умова має вигляд:
index < array.lengthА не:
index <= array.lengthОстанній варіант звернеться до неіснуючого елемента з індексом array.length.
Якщо дія має виконатися хоча б раз, while може бути неправильним вибором:
let attempts = 0;
while (attempts > 0) {
console.log("Ця дія не виконається");
}У такому сценарії розгляньте do...while.
Вкладені цикли іноді необхідні, але вони швидко ускладнюють код:
const matrix = [
[1, 2],
[3, 4],
];
for (const row of matrix) {
for (const value of row) {
console.log(value);
}
}Якщо вкладеність стає глибокою, винесіть частину логіки в окрему функцію або змініть структуру даних.
Видалення або додавання елементів до масиву під час перебору може призвести до пропущених елементів і неочікуваної поведінки. Краще:
створити новий масив через filter();
спочатку зібрати індекси для змін;
перебирати копію;
змінювати масив контрольовано через індексний for.
Перед написанням циклу поставте собі такі запитання:
Яку структуру даних я перебираю?
об’єкт — найчастіше for...in;
масив, рядок, Set або Map — найчастіше for...of.
Чи потрібен індекс або ручний контроль кроку?
так — for;
ні — for...of.
Чи відома кількість повторень?
так — for;
ні — while.
Чи потрібно виконати тіло до першої перевірки?
так — do...while.
Чи можна чітко описати задачу методом масиву?
так — розгляньте find, filter, map, some або інший відповідний метод.
Чи потрібно завершити перебір після знаходження результату?
використовуйте цикл із break або метод, який підтримує дострокове завершення, наприклад find.
for використовуйте для індексів, лічильників і точного контролю ітерацій.
while використовуйте, коли цикл залежить від умови, а кількість повторень невідома.
do...while використовуйте, коли тіло має виконатися щонайменше один раз.
for...of використовуйте для перебору значень масивів, рядків, Set, Map та інших ітерованих об’єктів.
for...in призначений для перебору ключів об’єкта, а не значень масиву.
break завершує цикл, а continue пропускає поточну ітерацію.
Для простих операцій над масивами іноді читабельнішими є map, filter, find, some, every або reduce.
Найкращий цикл — той, який найточніше описує мету коду та не приховує важливу логіку керування.