Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «defer проти async у тезі script». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Обидва атрибути дозволяють завантажувати скрипт паралельно з парсингом HTML, але відрізняються моментом виконання: `defer` виконує скрипт після повного парсингу HTML і строго в порядку оголошення, а `async` виконує його одразу після завантаження, у непередбачуваному порядку.** Звичайний `<script src="...">` без атрибутів зупиняє парсинг HTML, поки файл не завантажиться і не виконається, тому користувач бачить порожній екран. `defer` чекає на готовий DOM і затримує подію `DOMContentLoaded`, тому підходить для основної логіки застосунку, яка залежить від розмітки. `async` нічого не чекає і ні на що не впливає, тому підходить для незалежних сторонніх скриптів, наприклад аналітики. Обидва атрибути працюють лише для зовнішніх скриптів із `src`. ```html <script src="main.js" defer></script> <script src="analytics.js" async></script> ``` **Ключове:** `defer`, це паралельне завантаження плюс упорядковане виконання після побудови DOM, а `async`, це паралельне завантаження плюс виконання одразу, щойно файл прийшов.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`defer` і `async`, це атрибути тега `<script>`, які знімають блокування парсингу HTML: обидва завантажують файл паралельно з розміткою, але `defer` виконує скрипти після побудови DOM і в порядку оголошення, а `async` виконує кожен скрипт одразу після завантаження, у довільному порядку.** Саме момент виконання і є всією різницею між ними. ## Теорія ### TL;DR - Без атрибутів `<script src>` зупиняє парсинг HTML, це блокує рендер. - `defer`: завантаження паралельне, виконання після повного парсингу HTML, порядок зберігається. - `async`: завантаження паралельне, виконання одразу після завантаження, порядок не гарантується. - `defer`-скрипти виконуються до події `DOMContentLoaded`, і вона їх чекає; `async` її не чекає. - `defer`, це основна логіка сайту; `async`, це незалежні сторонні скрипти. - Обидва атрибути ігноруються для inline-скриптів без `src`. ### Швидкий приклад ```html <!-- HTML парситься... --> <script src="main.js" defer></script> <script src="analytics.js" async></script> ``` | Етап | Що відбувається | | --- | --- | | 1 | Браузер починає парсити HTML | | 2 | Одночасно завантажує обидва скрипти | | 3 | `analytics.js` (async) завантажився швидше, тому виконується одразу | | 4 | HTML допарсюється до кінця | | 5 | Потім виконується `main.js` (defer) | | 6 | Потім спрацьовує `DOMContentLoaded` | ### Проблема, яку розв'язують defer і async Коли браузер зустрічає тег: ```html <script src="script.js"></script> ``` за замовчуванням він: 1. Зупиняє парсинг HTML; 2. Завантажує `script.js`; 3. Виконує його; 4. І лише потім продовжує будувати DOM. Це блокує рендер сторінки, тому користувач бачить порожній екран, доки JS не завантажиться. Саме тому й з'явилися атрибути `defer` та `async`, щоб не блокувати парсинг HTML. ### Як працює defer ```html <script src="script.js" defer></script> ``` 1. Скрипт завантажується паралельно з HTML, асинхронно. 2. Але виконується лише після повного парсингу HTML, тобто коли DOM готовий, і до події `DOMContentLoaded`. 3. Скрипти з `defer` виконуються в тому порядку, у якому вони вказані в HTML. Це чудово підходить для основних скриптів застосунку, наприклад `main.js` чи `vendor.js`. ```html <script src="a.js" defer></script> <script src="b.js" defer></script> ``` Спочатку обидва завантажаться паралельно, але виконаються строго по черзі: спершу `a.js`, потім `b.js`, і вже після побудови DOM. Через це `defer` безпечний для коду, який одразу звертається до елементів сторінки, і для файлів, які залежать один від одного. ### Як працює async ```html <script src="script.js" async></script> ``` 1. Скрипт завантажується паралельно з HTML, так само як із `defer`. 2. Але виконується одразу після завантаження, навіть якщо HTML ще не допарсено. 3. Скрипти з `async` виконуються в непередбачуваному порядку, залежно від того, який швидше завантажиться. Підходить для незалежних скриптів, наприклад аналітики чи реклами. ```html <script src="a.js" async></script> <script src="b.js" async></script> ``` Тут `b.js` може виконатися раніше за `a.js`, якщо завантажився швидше, навіть якщо в HTML він стоїть нижче. Крім того, виконання `async`-скрипта перериває парсинг HTML у момент, коли файл прийшов, тому важкий `async`-скрипт усе одно може підгальмувати рендер. ### Порівняння | Властивість | `defer` | `async` | | --- | --- | --- | | Завантаження скрипта | Асинхронне, паралельно з HTML | Асинхронне | | Виконання | Після повного парсингу HTML | Одразу після завантаження | | Порядок виконання | Зберігається | Не гарантується | | Блокує парсинг HTML | Ні | Ні під час завантаження, але так у момент виконання | | Подія `DOMContentLoaded` | Чекає на всі `defer`-скрипти | Не чекає | | Типові сценарії | Основна логіка сайту | Зовнішні трекери, реклама | ### Що обирати на практиці | Варіант | Коли використовувати | | --- | --- | | `defer` | Для основного JS-коду, який залежить від DOM, наприклад ініціалізація інтерфейсу | | `async` | Для незалежних скриптів: аналітика, метрики, реклама | | Без атрибутів | Лише якщо справді треба заблокувати парсинг HTML, що потрібно рідко | Порада: якщо ви користуєтеся сучасним збирачем (Next.js, Vite, Webpack), він сам додає `defer` до `<script>` у `head`, тому в сучасних застосунках це вже поведінка за замовчуванням. Окремо варто пам'ятати, що `<script type="module">` поводиться як `defer` автоматично, а щоб отримати для модуля поведінку `async`, атрибут треба вказати явно. ### Типові помилки - **Вішати `defer` або `async` на inline-скрипт.** Без атрибута `src` обидва атрибути ігноруються, такий скрипт виконається синхронно на місці. - **Покладатися на порядок при `async`.** Якщо `b.js` потребує змінних із `a.js`, з `async` це зламається рандомно і лише на повільній мережі. - **Ставити `async` на код, що працює з DOM.** Скрипт може виконатися до того, як потрібний елемент з'явиться в документі, і `querySelector` поверне `null`. - **Плутати «не блокує парсинг» із «не блокує зовсім».** `async`-скрипт усе одно займає головний потік у момент виконання. - **Очікувати, що `DOMContentLoaded` дочекається `async`.** Подія може спрацювати раніше за такий скрипт, тому підписка на неї всередині `async`-файлу іноді просто не встигає. - **Залишати `document.write()` у відкладеному скрипті.** Після парсингу документа він перезапише сторінку, у `async` і `defer` його використовувати не можна.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.