Skip to main content

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
<!-- HTML парситься... --> <script src="main.js" defer></script> <script src="analytics.js" async></script>
ЕтапЩо відбувається
1Браузер починає парсити HTML
2Одночасно завантажує обидва скрипти
3analytics.js (async) завантажився швидше, тому виконується одразу
4HTML допарсюється до кінця
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-скрипт усе одно може підгальмувати рендер.

Порівняння

Властивістьdeferasync
Завантаження скриптаАсинхронне, паралельно з 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 його використовувати не можна.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.