Подієвий цикл: мікрозадачі та макрозадачі
Потік виконання в браузері, так само як і в Node.js, заснований на подієвому циклі.
Розуміння роботи подієвого циклу важливе для оптимізації, іноді для правильної архітектури.
У цьому розділі ми спочатку розберемо теорію, та був розглянемо її практичне застосування.
Подієвий цикл
Ідея подієвого циклу дуже проста. Є нескінченний цикл, в якому движок JavaScript чекає завдання, виконує їх і знову чекає на появу нових.
- Поки що є завдання:
- виконати їх, починаючи з найстарішої
- Не діяти до появи нового завдання, а потім перейти до пункту 1
Це формалізація того, що ми спостерігаємо, переглядаючи веб-сторінку. Двигун JavaScript більше часу нічого не робить і працює, тільки якщо потрібно виконати скрипт/обробник або обробити подію.
- Коли завантажується зовнішній скрипт, то завдання – це виконання цього скрипту.
- Коли користувач рухає мишу, завдання полягає в тому, щоб згенерувати подію мишівиконати і виконувати її обробники.
- Коли закінчиться таймер, встановлений за допомогою setTimeout(func, . ) , завдання – виконання функції func
- І так далі.
Завдання надходять на виконання – двигун виконує їх – потім чекає на нові завдання (під час очікування практично не навантажуючи процесор комп'ютера)
Може так статися, що завдання надходить, коли двигун зайнятий чимось іншим, тоді воно ставиться в чергу.
Чергу, яку формують такі завдання, називають чергою макрозадач (macrotask queue, термін V8).
Наприклад, коли двигун зайнятий виконанням скрипта, користувач може пересунути мишу, тим самим викликавши появу події mousemove , або може закінчитися таймер, встановлений setTimeout , і т.п.Ці завдання формують чергу, як показано на ілюстрації вище.
Завдання із черги виконуються за правилом «першим прийшов – першим пішов». Коли браузер закінчує виконання скрипта, він обробляє подію миші, потім виконує обробник, заданий setTimeout , і так далі.
Поки що все просто, чи не так?
- Рендеринг (малювання сторінки) ніколи не відбувається під час виконання завдання двигуном. Не має значення, наскільки довго виконується завдання. Зміни DOM відмальовуються тільки після того, як завдання виконано.
- Якщо завдання виконується дуже довго, то браузер не може виконувати інші завдання, обробляти події користувача, тому через деякий час браузер пропонує «вбити» завдання, що довго виконується. Таке можливо, коли у скрипті багато складних обчислень чи помилка, що веде до нескінченного циклу.
То була теорія. Тепер погляньмо, як можна застосувати ці знання.
Приклад 1: розбиття важкої задачі.
Припустимо, ми маємо завдання, що потребує значних ресурсів процесора.
Наприклад, підсвічування синтаксису (використовується для виділення кольором ділянок коду на цій сторінці) – досить процесороємне завдання. Для підсвічування коду треба виконати синтаксичний аналіз, створити багато елементів для виділення кольорів, додати їх у документ – для великого тексту це вимагає значних ресурсів.
Поки двигун зайнятий підсвічуванням синтаксису, він не може робити нічого, пов'язаного з DOM, не може обробляти події користувача і т.д. Можливо навіть "підвисання" браузера, що абсолютно неприйнятно.
Ми можемо уникнути цього, розбивши завдання на частини. Зробити підсвічування для перших 100 рядків, а потім запланувати setTimeout (з нульовою затримкою) для розмітки наступних 100 рядків і т.д.
Щоб продемонструвати такий підхід, давайте будемо використовувати для простоти функцію, яка рахує від 1 до 1000000000.
Якщо ви запустите код нижче, двигун "зависне" на деякий час. Для серверного JS це буде явно помітно, а якщо ви виконуватимете цей код у браузері, то спробуйте натискати інші кнопки на сторінці – ви помітите, що жодні інші події не обробляються до завершення функції рахунку.
let i = 0; let start = Date.now(); function count() < // робимо важку роботу for (let j = 0; j < 1e9; j++) < i++; >alert("Done in " + (Date.now() - start) + 'ms'); > count();
Браузер може навіть показати повідомлення "скрипт виконується надто довго".
Давайте розіб'ємо завдання на частини, скориставшись вкладеним setTimeout:
let i = 0; let start = Date.now(); function count() < // робимо частину важкої роботи (*) do < i++; >while (i % 1e6 != 0); if (i == 1e9) < alert("Done in " + (Date.now() - start) + 'ms'); >else < setTimeout(count); // плануємо новий виклик (**) >> count ();
Тепер інтерфейс браузера повністю працездатний під час виконання «рахунку».
Один виклик count робить частину роботи (*), а потім, якщо необхідно, планує свій черговий запуск (**):
- Перше виконання робить рахунок: i = 1 ... 1000000.
- Друге виконання робить рахунок: i = 1000001 ... 2000000.
- …і так далі.
Тепер якщо нова стороння задача (наприклад, подія onclick ) з'являється, поки двигун зайнятий виконанням 1-ї частини, вона стає в чергу, і потім виконується, коли 1-а частина завершена, перед наступною частиною. Періодичні повернення в подійний цикл між запусками count дають двигунові достатньо «повітря», щоб зробити щось ще, відреагувати на дії користувача.
Зазначимо, що обидва варіанти – з розбиттям завдання за допомогою setTimeout і без – можна порівняти за швидкістю виконання. Немає великої різниці у загальному часі рахунку.
Щоб скоротити різницю ще сильніше, давайте трохи покращимо наш код.
Ми перенесемо планування чергового виклику на початок count() :
let i = 0; let start = Date.now(); function count() < // перенесемо планування чергового виклику на початок if (i < 1e9 - 1e6) < setTimeout(count); // запланувати новий виклик >do < i++; >while (i % 1e6 != 0); if (i == 1e9) < alert("Done in " + (Date.now() - start) + 'ms'); >> count();
Тепер, коли ми починаємо виконувати count() і бачимо, що потрібно виконати count() ще раз, ми плануємо цей виклик негайно перед виконанням роботи.
Якщо ви запустите цей код, то легко помітите, що він потребує значно менше часу.
Все просто: як ви пам'ятаєте, в браузері є мінімальна затримка в 4 мілісекунди при множині вкладених викликів setTimeout. Навіть якщо ми вказуємо затримку 0, насправді вона дорівнюватиме 4 мс (або трохи більше). Тому що раніше ми заплануємо виконання – то швидше виконається код.
Отже, ми розбили ресурсомістку задачу на частини – тепер вона не блокує інтерфейс користувача, причому майже без втрат у загальному часі виконання.
Приклад 2: індикація прогресу
Ще одна перевага поділу на частини великої задачі у браузерних скриптах – це можливість показувати індикатор виконання.
Зазвичай браузер малює вміст сторінки після завершення виконання поточного коду. Немає значення, наскільки довго виконується завдання. Зміни в DOM відображаються лише після завершення.
З одного боку, це добре, тому що наша функція може створювати багато елементів, додавати їх по одному в документ і змінювати їх стилі – користувач не побачить проміжного, незакінченого стану. Це важливо, правда?
У прикладі нижче зміни i не будуть помітні, доки функція не завершиться, тому ми побачимо лише останнє значення i :
…Але, можливо, ми хочемо щось показати під час виконання завдання, наприклад, індикатор виконання.
Якщо ми розіб'ємо важке завдання на частини, використовуючи setTimeout , то зміни індикатора будуть відображені у проміжках між частинами.
while (i % 1e3! = 0); if (i < 1e7) < setTimeout(count); >> count();
Тепер показує зростання значення i – це свого роду індикатор виконання.
Приклад 3: робимо щось після події
В обробнику події ми можемо вирішити відкласти деякі дії, поки подія не «випливе» і не буде оброблена на всіх рівнях. Ми можемо досягти цього, обернувши код у setTimeout з нульовою затримкою.
У розділі Генерація користувачів подій ми бачили приклад: наша подія menu-open генерується через setTimeout , щоб вона виникла після того, як повністю оброблена подія «click».
menu.onclick = function() < // . // Створимо нашу власну подію з даними пункту меню, за яким клацнули мишею let customEvent = new CustomEvent("menu-open", <bubbles: true>); // згенерувати нашу подію асинхронно setTimeout(() => menu.dispatchEvent(customEvent)); >;
Макрозадачі та Мікрозадачі
Крім макрозавдань, описаних у цій частині, існують мікрозавдання, згадані у розділі Мікрозавдання.
Мікрозадачі надходять лише з коду. Зазвичай вони створюються промісами: виконання обробника .then/catch/finally стає мікрозавданням.Мікрозадачі також застосовуються «під капотом» await , т.к. це форма обробки промісу.
Також є спеціальна функція queueMicrotask(func), яка поміщає func у чергу мікрозадач.
Відразу після кожної макрозавдання двигун виконує всі завдання з черги мікрозавдань перед тим, як виконати наступне макрозавдання або відобразити зміни на сторінці, або зробити щось ще.
setTimeout(() => alert("timeout")); Promise.resolve() .then(() => alert("promise")); alert("code");
Який тут буде лад?
- code утворюється першим, т.к. це звичайний синхронний дзвінок.
- promise з'являється другим, тому що .then проходить через чергу мікрозадач і виконується після синхронного поточного коду.
- timeout з'являється останнім, тому що це макрозавдання.
Докладніше зображення подієвого циклу виглядає так:
Усі мікрозавдання завершуються до обробки будь-яких подій або рендерингу, або переходу до іншого макрозадачі.
Це важливо, оскільки гарантує, що загальне оточення залишається одним і тим самим між мікрозадачами – не змінено координати миші, не отримано нових даних по мережі тощо.
Якщо ми хочемо запустити функцію асинхронно (після поточного коду), але до відображення змін і нових подій, то можемо запланувати це через queueMicrotask .
Ось приклад з індикатором виконання, схожий на попередній, але цього разу використано функцію queueMicrotask замість setTimeout . Зверніть увагу – малювання сторінки відбувається лише наприкінці. Як і у випадку звичайного синхронного коду.
while (i % 1e3! = 0); if (i < 1e6) < queueMicrotask(count); >> count();
Разом
Більш докладний алгоритм подієвого циклу (хоч і спрощений порівняно зі специфікацією):
- Вибрати та виконати найстаріше завдання з черги макрозавдань (наприклад, "script").
- Виконати все мікрозавдання:
- Поки що черга мікрозадач не порожня: - Вибрати з черги і виконати найстаріше мікрозавдання
- Відображайте зміни сторінки, якщо вони є.
- Якщо черга макрозадач порожня – почекати, доки з'явиться макрозавдання.
- Перейти до кроку 1.
Щоб додати до черги нову макрозавдання:
Цей спосіб можна використовувати для розбиття великих обчислювальних завдань на частини, щоб браузер міг реагувати на події користувача і показувати прогрес виконання цих частин.
Також це використовується в обробниках подій для відкладеного виконання дії після того, як подія повністю оброблена (спливання завершено).
Для додавання до черги нової мікрозавдання:
- Використовуйте queueMicrotask(f).
- Також обробники промісів виконуються у рамках черги мікрозавдань.
Події інтерфейсу користувача та мережні події в проміжках між мікрозадачами не обробляються: мікрозавдання виконуються безперервно одна за одною.
Тому queueMicrotask можна використовувати для асинхронного виконання функції у тому ж стані оточення.
Для тривалих важких обчислень, які не повинні блокувати цикл подій, ми можемо використовувати Web Workers.
Це спосіб виконати код в іншому паралельному потоці.
Web Workers можуть обмінюватися повідомленнями з основним процесом, але вони мають свої змінні та свій подійний цикл.
Web Workers немає доступу до DOM, тому основне їх застосування – обчислення.
Цикл
Програмування побудоване на повторному виконанні коду.
Час читання: менше 5 хв
Коротко
Скопіювати посилання "Кратко" Скопійовано
Цикл - це послідовність дій, що повторюється.
Цикл складається з умови і тіла циклу.
Перед запуском циклу перевіряється умова. Якщо умова дійсна, то виконується блок коду, який називається тілом циклу. Потім цей крок повторюється. Так триватиме, доки умова не стане хибною.
Кожне виконання тіла циклу називається ітерацією.
JavaScript надає кілька способів створення циклу. Найпоширеніші їх — while і for (ініціалізація; умова; завершальна операція) <>.
Як зрозуміти
Скопіювати посилання "Як зрозуміти" Скопійовано
Навіщо потрібні цикли читайте у статті «Порядок виконання».
У програмуванні є багато завдань, коли потрібно виконувати наперед невідому кількість однотипних кроків:
- надрукувати всі повідомлення зі списку;
- позначити на карті надіслані користувачем координати;
- уточнювати значення при обчисленні функцій (наприклад, рахувати квадратний корінь).
До виконання програми неможливо знати скільки буде повідомлень або скільки разів потрібно уточнити значення для точного розрахунку квадратного кореня. Такі завдання розв'язують цикли.
Цикли виконують блок коду доти, доки виконується умова. За допомогою умови можна керувати тим, скільки разів виконувати код, який називається тілом циклу. Наприклад, виконувати код, доки всі повідомлення не будуть надруковані.
Цикл завершується, коли умова перестає виконуватись. Такого може і не статися і цикл працюватиме нескінченно. Зазвичай нескінченний цикл результат помилки програміста. Програма зависає, оскільки тіло циклу виконуватиметься вічно.
Опишемо роботу циклу словами.
- Перевір, чи виконується умова.
- Якщо умова виконується, виконайте тіло циклу. Потім повернися до пункту 1.
- Якщо умова не виконується, цикл завершено.
Цикл зручно представляти як схеми:
Умова
Скопіювати посилання "Умова" Скопійовано
Потрібно добре розуміти, як працює умова, щоб впевнено працювати із циклами. Умова – це вираз, який JavaScript обчислить у значення.
У разі, умова обчислюється в логічний тип: true , чи false . Такі вирази виходять при використанні операторів порівняння = = , = = = , > , < , >= , < = , ! = = ,! =.
let count = 10while (count > 0) console.log(count)
count-->
let count =
10
while
(count >
0)
console.log(count) count--
>
Код друкує числа від 10 до 1 на екрані.
Висловлювання за умови можна комбінувати за допомогою логічних операторів.
У складнішому разі умова буде обчислюватися на будь-який інший тип: число, рядок, масив, об'єкт тощо. буд. У цьому випадку JavaScript буде наводити значення до логічного типу.
let count = 10
while (count) console.log(count)
count-->
let count =
10
while
(count)
console.log(count) count--
>
Щоб зрозуміти, який цикл колись зупиниться, потрібно запам'ятати правила приведення різних типів до логічного. Головне правило:
Все, що не приводиться до false, буде true.
Залишилося запам'ятати 8 значень, що наводяться до false :
Знаючи це правило, ми зрозуміємо, що цикл перестане працювати після 10 ітерацій та надрукує числа від 10 до 1.
Тіло циклу
Скопіювати посилання "Тіло циклу" Скопійовано
Тіло циклу - це набір інструкцій, укладений у фігурні дужки. Одне виконання тіла називають ітерацією циклу.
Обмежень на операції у тілі циклу немає. Тіло може містити оголошення змінних, виклики функцій, інші керуючі конструкції та цикли.
Нескінченні цикли
Скопіювати посилання "Нескінченні цикли" Скопійовано
Якщо умова циклу написано так, що вона ніколи не стане хибною, цикл виконуватиметься нескінченно.
Такий цикл займає всі виділені ресурси комп'ютера. У результаті вкладка браузера або програма зависає.
Натисніть кнопку «Натисніть на мене», і на екрані з'явиться повідомлення. Якщо ж натиснути кнопку "Стартувати нескінченний цикл", то "Натисні на мене" перестане працювати - у системи не буде ресурсів обробити натискання, тому що всі ресурси займе нескінченний цикл.
До нескінченного циклу можуть призвести дві помилки:
- неправильна умова;
- Умова написана правильно, але в тілі циклу не змінюються змінні, які застосовуються в умові.
На практиці
Скопіювати посилання "На практиці" Скопійовано
Микола Лопін радить
Скопіювати посилання "Микола Лопін радить" Скопійовано
🛠 Складайте умову так, щоб вона змінювалася після кожної ітерації циклу. Кожна ітерація має наближати момент зупинення циклу.
🛠 Всередині циклу можна використовувати оператор break, він перериває цикл:
let count = 5while (count > 0) if (count === 2) // Цикл перерветься тут, оператор break вбиває цикл
break
>
console.log(count)
count-->// 5, 4, 3
let count =
5
while
(count >
0)
if
(count ===
2)
// Цикл перерветься тут, оператор break вбиває цикл
break
>
console.log(count) count--
>
// 5, 4, 3
break намагаються не використовувати, тому що він веде себе як друга умова циклу та знижує читаність. У більшості випадків цикл з break можна переписати без цього оператора, змінивши умову або додавши додаткову перевірку:
let count = 5while (count > 2) console.log(count)
count-->// Надрукує 5, 4, 3
let count =
5
while
(count >
2)
console.log(count) count--
>
// Надрукує 5, 4, 3
🛠 Всередині циклу можна використовувати оператор continue. Він перериває поточну ітерацію та повертається до перевірки умови:
let count = 5while (count > 0) count--
if (count === 2) // Коли count дорівнюватиме 2, ітерація перерветься тут
// і не дійде до друку
continue
>
console.log(count)>// Надрукує 4, 3, 1, 0
let count =
5
while
(count >
0)
count--
if
(count ===
2)
// Коли count дорівнюватиме 2, ітерація перерветься тут
// і не дійде до друку
continue
>
console.log(count)
>
// Надрукує 4, 3, 1, 0
Оператор намагаються не використовувати, тому що він ускладнює читання коду – з'являється ще одне місце, де може завершитися ітерація. Майже завжди цикл із continue можна переписати на цикл без нього, додавши розгалуження if :
let count = 5while (count > 0) count--
if (count !== 2) console.log(count)
>>// Надрукує 4, 3, 1, 0
let count =
5
while
(count >
0)
count--
if
(count !==
2)
console.log(count)
>
>
// Надрукує 4, 3, 1, 0
Цикли while та for
При написанні скриптів часто постає завдання зробити однотипну дію багато разів.
Наприклад, вивести товари зі списку один за одним. Або просто перебрати всі числа від 1 до 10 та для кожного виконати однаковий код.
Для багаторазового повторення однієї ділянки коду передбачено цикли.
Невелике оголошення для сучасних читачів.
У статті розглядаються лише базові цикли: while , do..while і for(..;..;..) .
Якщо ви прийшли до цієї статті у пошуках інших типів циклів, ось покажчики:
- для перебору властивостей об'єкта.
- Див. for…of та Об'єкти, що перебираються, для перебору масивів і об'єктів, що перебираються.
Інакше продовжуйте читати.
Цикл "while"
Цикл while має наступний синтаксис:
Код з тіла циклу виконується, поки умова умов істинно.
Наприклад, цикл нижче виводить i поки i < 3 :
Одне виконання тіла циклу по-науковому називається ітерація. Цикл у прикладі вище робить три ітерації.
Якби рядок i++ був відсутній у прикладі вище, то цикл би повторювався (теоретично) вічно. На практиці, звичайно, браузер не дозволить такому статися, він надасть користувачеві можливість зупинити скрипт, що «підвис», а JavaScript на стороні сервера доведеться «вбити» процес.
Будь-яке вираз або змінна може бути умовою циклу, а не тільки порівняння: умова, коли обчислюється і перетворюється на логічне значення.
Наприклад, while (i) - більш короткий варіант while (i! = 0):
Якщо тіло циклу складається лише з однієї інструкції, ми можемо опустити фігурні дужки:
let i = 3; while (i) alert(i--);
Цикл «do…while»
Перевірку умови можна розмістити під тілом циклу, використовуючи спеціальний синтаксис do..while:
Цикл спочатку виконає тіло, а потім перевірить умову condition, і доки його значення дорівнює true, він буде виконуватися знову і знову.
Така форма синтаксису виправдана, якщо ви хочете, щоб тіло циклу виконалося хоча б один разнавіть якщо умова виявиться хибною. Насправді частіше використовується форма з передумовою: while(…) .
Цикл «for»
Більш складний, але при цьому найпоширеніший цикл – цикл for.
Давайте розберемося, що означає кожна частина на прикладі. Цикл нижче виконує alert(i) для i від 0 до (але не включаючи) 3 :
Розглянемо конструкцію для детальніше:
| частина | ||
|---|---|---|
| початок | let i = 0 | Виконується один раз при вході в цикл |
| умова | i < 3 | Перевіряється перед кожною ітерацією циклу. Якщо воно обчислиться в false, цикл зупиниться. |
| тіло | alert(i) | Виконується знову і знову, доки умова обчислюється в true . |
| крок | i++ | Виконується після тіла циклу на кожній ітерації перед перевіркою умови. |
В цілому алгоритм роботи циклу виглядає наступним чином:
Виконати початок → (Якщо умова == true → Виконати тіло, Виконати крок) → (Якщо умова == true → Виконати тіло, Виконати крок) → (Якщо умова == true → Виконати тіло, Виконати крок) → .
Тобто, початок виконується один раз, а потім кожна ітерація полягає у перевірці умови, після якої виконується тіло та крок.
Якщо тема циклів для вас нова, може бути корисним повернутись до прикладу вище і відтворити його роботу на аркуші паперу, крок за кроком.
Ось точно те, що відбувається в нашому випадку:
// for (let i = 0; i < 3; i++) alert(i) // Виконати початок let i = 0; // Якщо умова == true → Виконати тіло, Виконати крок if (i < 3) < alert(i); i++ >// Якщо умова == true → Виконати тіло, Виконати крок if (i < 3) < alert(i); i++ >// Якщо умова == true → Виконати тіло, Виконати крок if (i < 3) < alert(i); i++>//. кінець, тому що тепер i == 3
У прикладі змінна лічильника i була оголошена у циклі. Це так зване "вбудоване" оголошення змінної. Такі змінні існують лише всередині циклу.
for (let i = 0; i < 3; i++) < alert(i); // 0, 1, 2> alert (i); // помилка, немає такої змінної
Замість оголошення нової змінної ми можемо використовувати існуючу:
let i = 0; for (i = 0; i < 3; i++) < // використовуємо існуючу змінну alert(i); // 0, 1, 2> alert (i); // 3, змінна доступна, т.к. була оголошена зовні циклу
Перепустка частин «for»
Будь-яка частина for може бути пропущена.
Наприклад, ми можемо пропустити початок, якщо нам нічого не потрібно робити перед стартом циклу.
let i = 0; // ми вже маємо оголошену i з присвоєним значенням for (; i < 3; i++) < // немає потреби у "початку" alert( i ); // 0, 1, 2 >
Це зробить цикл аналогічним while (i < 3).
А можна і взагалі прибрати все, отримавши нескінченний цикл:
При цьому самі крапки з комою; обов'язково повинні бути присутніми, інакше буде помилка синтаксису.
Переривання циклу: "break"
Зазвичай цикл завершується при обчисленні умови у false.
Але ми можемо вийти з циклу у будь - який момент за допомогою спеціальної директиви break .
Наприклад, наступний код підраховує суму чисел, що вводяться, доки відвідувач їх вводить, а потім – видає:
let sum = 0; while (true) < let value = + prompt("Введіть число", ''); if (! value) break; //(*) sum += value; >alert('Сума:' + sum);
Директива break у рядку (*) повністю припиняє виконання циклу і передає управління на рядок за його тілом, тобто alert .
Взагалі, поєднання "нескінченний цикл + break" - відмінна штука для тих ситуацій, коли умова, за якою потрібно перерватися, знаходиться не на початку або наприкінці циклу, а посередині або навіть у кількох місцях його тіла.
Перехід до наступної ітерації: continue
Директива continue - "полегшена версія" break. При її виконанні цикл не переривається, а переходить до наступної ітерації (якщо умова все одно true ).
Її використовують, якщо відомо, що у поточному повторі циклу робити нічого.
Наприклад, цикл нижче використовує continue , щоб виводити лише непарні значення:
Для парних значень i директива continue припиняє виконання тіла циклу і передає управління на наступну ітерацію for (з наступним числом). Таким чином, alert викликається тільки для непарних значень.
Цикл, який обробляє лише непарні значення, міг би виглядати так:
З технічного погляду він цілком ідентичний. Справді, замість continue можна просто загорнути дії блоку if .
Однак, ми отримали додатковий рівень вкладеності фігурних дужок. Якщо код всередині if більш довгий, це погіршує читаність, на відміну варіанта з continue .
Зверніть увагу, що ці синтаксичні конструкції не є виразами та не можуть бути використані з тернарним оператором? . Зокрема, використання таких директив, як break/continue, викликає помилку.
Наприклад, якщо ми візьмемо цей код:
…і перепишемо його, використовуючи знак запитання:
(i > 5)? alert(i) : continue; // continue тут призведе до помилки
…то буде синтаксична помилка.
Це ще один привід не використовувати оператор знаку питання? замість if.
Мітки для break/continue
Буває, потрібно вийти одночасно з кількох рівнів циклу одразу.
Наприклад, у коді нижче ми проходимося циклами по i і j, запитуючи за допомогою prompt координати (i, j) з (0,0) до (2,2):
for (let i = 0; i < 3; i++) < for (let j = 0; j < 3; j++) < let input = prompt(`Значення на координатах ($,$)`, ''); // Що якщо ми захочемо перейти до Готово (нижче) прямо звідси? > > alert('Готово!');
Нам потрібен спосіб зупинити виконання, якщо користувач скасує введення.
Звичайний break після input лише перерве внутрішній цикл, але цього недостатньо. Досягти бажаної поведінки можна за допомогою міток.
Мітка має вигляд ідентифікатора з двокрапкою перед циклом:
Виклик break у циклі нижче шукає найближчий зовнішній цикл із такою міткою та переходить у його кінець.
outer: for (let i = 0; i < 3; i++) < for (let j = 0; j < 3; j++) < let input = prompt(`Значення на координатах ($,$)`, '');// якщо порожній рядок або Скасувати, вийти з обох циклів if (!input) break outer; // (*) // зробити щось зі значеннями. > > alert('Готово!');
У прикладі це означає, що викликом break outer буде розірваний зовнішній цикл до мітки з ім'ям outer .
Таким чином керування перейде з рядка, поміченого (*), до alert('Готово!') .
Можна розміщувати мітку на окремому рядку:
Директива continue також може бути використана з позначкою. У цьому випадку керування перейде на наступну ітерацію циклу з міткою.
Мітки не дозволяють передавати управління в довільне місце коду.
Наприклад, немає можливості зробити таке:
break label; // не стрибає до мітки нижче за label: for (. )
Директива break повинна бути всередині блоку коду. Технічно підійде будь-який маркований блок коду, наприклад:
…Хоча у 99.9% випадків break використовується всередині циклів, як ми бачили у прикладах вище.
До речі, continue можливе лише всередині циклу.
Разом
Ми розглянули 3 види циклів:
- while – Перевіряє перед кожною ітерацією.
- do..while – Перевіряє умову після кожної ітерації.
- for (;;) – Перевіряє умову перед кожною ітерацією, чи є можливість задати додаткові налаштування.
Щоб організувати нескінченний цикл, використовують конструкцію while (true). При цьому він, як і будь-який інший цикл, може бути перерваний директивою break.
Якщо на цій ітерації циклу робити більше нічого не треба, але повністю припиняти цикл не слід - використовують директиву continue.
Обидві ці директиви підтримують мітки, що ставляться перед циклом. Мітки – єдиний спосіб для break/continue вийти межі поточного циклу, вплинути виконання зовнішнього.
Зауважимо, що мітки не дозволяють стрибнути в довільне місце коду, JavaScript немає такої можливості.
