Основи циклів у TypeScript: while, do-while, for, break та continue
Дізнайтеся, як цикли while, do-while та for у TypeScript оцінюють свої умови, чому зміна області видимості змінної циклу впливає на результати, та як оператори break та continue змінюють їхній хід.
Джерела: Mastering Loops in TypeScript: while, do-while, for, break & continue
Цикли дозволяють багаторазово виконувати один і той самий блок коду, поки діє певна умова, замість того щоб постійно копіювати одну й ту саму інструкцію. Вони здаються простими, але більшість помилок у циклах виникають через три аспекти: коли перевіряється умова, де знаходиться змінна циклу та де розташовані оператори break чи continue у тілі циклу. У цьому посібнику розглядаються цикли while, do-while та for у TypeScript із невеликими прикладами, результати виконання яких можна передбачити крок за кроком, щоб ви могли читати та писати код ітерацій без несподіванок.
Чому ітерації кращі за повторення
Для виведення чисел від 1 до 10 без циклу потрібно десять викликів console.log. За допомогою циклу ви описуєте завдання один раз, а система його повторює, і розмір коду залишається незмінним, чи потрібно десять, чи десять тисяч повторень.
Цикл while: спочатку перевірка, потім виконання
Цикл while має найпростішу структуру з усіх:
while (condition) {
// statements
}
Умова оцінюється до кожного проходження циклу. Поки вона дорівнює true, виконується тіло циклу; як тільки вона стає false, цикл завершується. Для функціонування циклу while потрібні три елементи:
- початкове значення змінної, від якої залежить умова;
- сама умова;
- щось у тілі циклу, що змушує цю змінну переходити в стан, коли умова буде
false.
Якщо пропустити третій елемент, цикл ніколи не зупиниться — про цю проблему йтиметься нижче.
Підрахунок від 1 до 10
Тут видно всі три елементи: змінна i починається з 1, цикл виконується поки i <= 10, а оператор i++ збільшує її значення після кожного проходження:
let i: number = 1; // initialization
while (i <= 10) {
console.log(i);
i++;
}
Фільтрація парних чисел
Щоб надрукувати лише парні числа в цьому діапазоні, продовжуйте переглядати кожне значення, але додайте перевірку всередині коду. Оператор модуля % повертає залишок від ділення, тому i % 2 == 0 є істинним саме тоді, коли i є парним:
let i: number = 1;
while (i <= 10) {
if (i % 2 == 0) {
console.log(i);
}
i++;
}
Існує простіший спосіб досягти схожого результату: повністю пропустіть перевірку та збільшуйте значення на два кожного разу. Зауважте, що ця версія починається з 0, тому її результат також включає 0:
let i: number = 0;
while (i <= 10) {
console.log(i);
i = i + 2;
}
Результат: 0, 2, 4, 6, 8, 10
Фільтрація непарних чисел
Якщо змінити умову на != 0, будуть обрані непарні значення. Початок з 0 тут не має значення, оскільки 0 є парним та фільтрується:
let i: number = 0;
while (i <= 10) {
if (i % 2 != 0) {
console.log(i);
}
i++;
}
Результат: 1, 3, 5, 7, 9
У цих прикладах використовуються == та !=; зазвичай віддають перевагу строгим формам === та !==, оскільки вони ніколи не змінюють типи, хоча для двох чисел результат буде ідентичним.
Зворотний підрахунок
Щоб рахувати назад, почніть з великого числа та зменшуйте його, поки значення становить принаймні 1:
let i: number = 10;
while (i >= 1) {
console.log(i);
i--;
}
Нескінченні цикли та причини їх виникнення
Умова while — це просто вираз, який повертає значення типу boolean, тому while (true) є абсолютно допустимим. Наведений нижче цикл буде друкувати інформацію вічно, оскільки немає нічого, що могло б його завершити:
let i: number = 1;
while (true) {
console.log(i);
i++;
// no break, no exit condition this runs forever
}
Частіше всього нескінченні цикли виникають випадково: змінна-контролер ніколи не оновлюється або змінюється у неправильному напрямку. Неконтрольований цикл блокує все інше у своїй потоці, тому вкладка браузера чи процес Node.js здається завислим. Навмисний використання while (true) є прийнятним, якщо він поєднується з чітким способом виходу, зазвичай break (описано нижче), наприклад у логіці «спробувати знову, поки не вдасться».
Цикл do-while: виконується один раз, потім перевірка
У циклі do-while умова перевіряється в кінці:
do {
// statements
} while (condition);
Оскільки перевірка відбувається після виконання тіла циклу, тіло завжди виконується принаймні один раз, незалежно від значення умови. На відміну від цього, звичайний while може виконуватися нуль разів.
Якщо умова спочатку є істинною, do-while поводиться так само, як і while:
let i: number = 1;
do {
console.log(i);
i++;
} while (i <= 5);
Різниця проявляється тоді, коли умова з самого початку є хибною:
let i: number = 10;
do {
console.log(i);
i++;
} while (i <= 5);
Результат: 10
Тіло циклу виконується без умов, виводить 10 та збільшує значення i до 11. Лише після цього оцінюється умова i <= 5; вона є хибною, тому цикл завершується після однієї ітерації. Цикл while (i <= 5), який починається з 10, нічого не виведе. Використовуйте do-while, коли необхідно здійснити принаймні одну ітерацію, наприклад для відображення запиту принаймні один раз.
Цикл for: все в одному заголовку
Цикл for — це той, яким ви будете користуватися найчастіше, особливо коли заздалегідь відомо, скільки разів потрібно повторити операції чи на скільки має змінитися лічильник. Він об’єднує ініціалізацію, умову та оновлення в один заголовок, що робить його компактнішим порівняно з еквівалентним циклом while:
for (initialization; condition; increment/decrement) {
// statements
}
Порядок виконання цих етапів варто запам’ятати:
- Ініціалізація виконується один раз, перед усім іншим (наприклад
let i = 1). - Оцінюється умова. Якщо вона дорівнює
true, виконується тіло циклу; якщоfalse, цикл закінчується. - Після виконання тіла виконується оператор оновлення (наприклад
i++). - Контроль повертається до кроку 2, і цикл повторюється доти, доки умова не стане
false.
Іншими словами, ініціалізація відбувається лише один раз, тоді як оцінка умови та оновлення виконуються щоразу під час циклу.
Підрахунок від 1 до 10
for (let i = 1; i <= 10; i++) {
console.log(i);
}
Два способи отримання парних чисел
Як і у випадку з while, можна просуватися на два кроки за раз:
for (let i = 2; i <= 10; i += 2) {
console.log(i);
}
або обробляти кожне значення та фільтрувати їх усередині тіла циклу:
for (let i = 2; i <= 10; i++) {
if (i % 2 == 0) {
console.log(i);
}
}
Обидва способи виводять однакову послідовність:
2
4
6
8
10
Шаговий підхід виконує менше операцій, оскільки перевіряє лише парні значення; фільтрація є більш гнучкою, коли правило не є фіксованим кроком, наприклад «ділиться на 3 або 7».
Зворотний підрахунок
Оновлення зі зменшенням виконує цикл у зворотному порядку:
for (let i = 10; i >= 1; i--) {
console.log(i);
}
Результат: 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
Чому змінна циклу дорівнює 6 після завершення циклу
Місце оголошення змінної циклу впливає на її значення після завершення циклу. У цьому прикладі i оголошується до початку циклу, а блок for лише присвоює їй значення:
let i: number;
for (i = 1; i <= 5; i++) {
console.log(i);
}
console.log(i);
Результат: 1, 2, 3, 4, 5, 6
Оскільки i знаходиться у зовнішньому області видимості, останній виклик console.log(i) все одно може до нього дістатися, зберігаючи значення, яке залишило цикл. Це 6, а не 5, через порядок виконання операцій: під час останнього проходу i дорівнює 5, тіло циклу виводить його, наступна операція збільшує його до 6, і лише тоді умова 6 <= 5 виявляється неправдивою, що призводить до завершення циклу.
Більш поширений стиль — for (let i = 1; i <= 5; i++) — надає i область видимості блоку, обмежену циклом, тож після нього воно більше не існує. Звернення до нього після циклу є помилкою на етапі компіляції в TypeScript (“Cannot find name 'i'”), а у звичайному JavaScript це спричинить виникнення ReferenceError під час виконання. Зазвичай краще оголошувати змінну у заголовку; використовуйте зовнішнє оголошення лише тоді, коли вам потрібне кінцеве значення.
Зміна ходу циклу за допомогою break та continue
Дві конструкції для переривання дозволяють змінити звичайний перебіг циклу безпосередньо всередині його тіла:
breakнегайно завершує цикл, незалежно від кількості залишкових ітерацій.continueприпиняє лише поточну ітерацію та переходить до наступної; цикл продовжує працювати.
У циклі for конструкція continue все одно виконує вираз оновлення перед повторною перевіркою умови, тому лічильник продовжує зростати.
break: місце розташування всередині тіла має значення
Тут перевірка break виконується перед виведенням даних:
let i: number;
for (i = 1; i <= 10; i++) {
if (i == 5) {
break;
}
console.log(i);
}
Результат: 1, 2, 3, 4
Коли i досягає значення 5, цикл завершується раніше, ніж у console.log з’являється можливість виконатися, тому число 5 так і не з’являється.
Якщо перемістити виведення перед перевіркою, результат зміниться:
let i: number;
for (i = 1; i <= 10; i++) {
console.log(i);
if (i == 5) {
break;
}
}
Результат: 1, 2, 3, 4, 5
Умова залишилася незмінною, проте тепер з’являється 5, оскільки функція print виконується до перевірки. Як у випадку з break, так і з continue, порядок операторів усередині тіла циклу визначає результат.
continue: пропуск одного значення
Заміна break на continue перетворює «зупинитися на 5» на «пропустити 5»:
let i: number;
for (i = 1; i <= 10; i++) {
if (i == 5) {
continue;
}
console.log(i);
}
Результат: 1, 2, 3, 4, 6, 7, 8, 9, 10
Цикл продовжується; просто для цієї ітерації не виконується функція print.
Пропуск усіх непарних чисел
У поєднанні з фільтром continue відправляє кожне непарне значення безпосередньо до наступної ітерації:
let i: number;
for (i = 0; i <= 10; i++) {
if (i % 2 != 0) {
continue;
}
console.log(i);
}
Результат: 0, 2, 4, 6, 8, 10
Пропуск списку конкретних значень
Умова може бути настільки складною, наскільки це необхідно. Використовуючи логічний оператор OR ||, цей цикл пропускає числа 3, 5 та 7:
let i: number;
for (i = 0; i <= 10; i++) {
if (i == 3 || i == 5 || i == 7) {
continue;
}
console.log(i);
}
Результат: 0, 1, 2, 4, 6, 8, 9, 10
Для довшого списку пропускання краще використовувати [3, 5, 7].includes(i) або структуру Set.
Основні висновки
- У циклі
whileумова перевіряється перед кожним проходом, і він може не виконатися жодного разу; у цикліdo-whileперевірка відбувається після цього, тож він завжди виконується принаймні один раз. - Кожен цикл потребує оновлення, яке зрештою робить умову хибною; інакше виникне нескінченний цикл, який заблокує потік виконання.
- У циклі
forініціалізація виконується один раз, тоді як умова та оновлення — щоразу під час циклу, тому лічильник досягає значення на один крок понад заданий ліміт.
let у початку циклу зберігає його в межах цього циклу; оголошення його поза ним дозволяє зберегти його кінцеве значення.break завершує цикл, а continue пропускає одну ітерацію; місце їх розташування відносно інших операторів впливає на результат.Умовні оператори визначають, який код буде виконуватися; цикли визначають, як часто. Разом вони охоплюють більшу частину логіки керування, яку ви будете писати.