defer проти async у тезі script
defer і async, це атрибути тега <script>, які знімають блокування парсингу HTML: обидва завантажують файл паралельно з розміткою, але defer виконує скрипти після побудови DOM і в порядку оголошення, а async виконує кожен скрипт одразу після завантаження, у довільному порядку. Саме момент виконання і є всією різницею між ними.
Теорія
TL;DR
- Без атрибутів
<script src>зупиняє парсинг HTML, це блокує рендер. defer: завантаження паралельне, виконання після повного парсингу HTML, порядок зберігається.async: завантаження паралельне, виконання одразу після завантаження, порядок не гарантується.defer-скрипти виконуються до подіїDOMContentLoaded, і вона їх чекає;asyncїї не чекає.defer, це основна логіка сайту;async, це незалежні сторонні скрипти.- Обидва атрибути ігноруються для inline-скриптів без
src.
Швидкий приклад
<!-- 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
Коли браузер зустрічає тег:
<script src="script.js"></script>за замовчуванням він:
- Зупиняє парсинг HTML;
- Завантажує
script.js; - Виконує його;
- І лише потім продовжує будувати DOM.
Це блокує рендер сторінки, тому користувач бачить порожній екран, доки JS не завантажиться. Саме тому й з'явилися атрибути defer та async, щоб не блокувати парсинг HTML.
Як працює defer
<script src="script.js" defer></script>- Скрипт завантажується паралельно з HTML, асинхронно.
- Але виконується лише після повного парсингу HTML, тобто коли DOM готовий, і до події
DOMContentLoaded. - Скрипти з
deferвиконуються в тому порядку, у якому вони вказані в HTML.
Це чудово підходить для основних скриптів застосунку, наприклад main.js чи vendor.js.
<script src="a.js" defer></script>
<script src="b.js" defer></script>Спочатку обидва завантажаться паралельно, але виконаються строго по черзі: спершу a.js, потім b.js, і вже після побудови DOM. Через це defer безпечний для коду, який одразу звертається до елементів сторінки, і для файлів, які залежать один від одного.
Як працює async
<script src="script.js" async></script>- Скрипт завантажується паралельно з HTML, так само як із
defer. - Але виконується одразу після завантаження, навіть якщо HTML ще не допарсено.
- Скрипти з
asyncвиконуються в непередбачуваному порядку, залежно від того, який швидше завантажиться.
Підходить для незалежних скриптів, наприклад аналітики чи реклами.
<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його використовувати не можна.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.