Атрибут defer у script
defer, це атрибут теґу <script>, який каже браузеру: завантаж цей скрипт паралельно з HTML, але виконай його лише після того, як увесь HTML-документ буде завантажено і розібрано. Виконання відбувається перед подією DOMContentLoaded, тому DOM на цей момент уже повністю побудований.
Теорія
TL;DR
- Без атрибутів
<script>зупиняє парсинг HTML на час завантаження і виконання файлу. deferзавантажує скрипт паралельно з розбором HTML і не блокує його.- Виконання відкладається до моменту, коли DOM побудовано, і відбувається перед
DOMContentLoaded. - Кілька
defer-скриптів виконуються в порядку появи в розмітці; вasyncпорядок довільний. deferу<head>, це стандартний спосіб підключати основні скрипти застосунку.deferдіє тільки для зовнішніх скриптів зsrc, для інлайнового коду він ігнорується.
Швидкий приклад
<head>
<!-- завантажується паралельно з HTML, виконується вже на готовому DOM -->
<script src="main.js" defer></script>
</head>
<body>
<p>Привіт!</p>
</body>Звичайна поведінка без defer
<script src="app.js"></script>Що відбувається:
- Браузер починає завантажувати HTML.
- Зустрічає
<script>. - Зупиняє парсинг HTML, щоб:
- скачати
app.js, - виконати його.
- скачати
- Тільки потім продовжує будувати сторінку.
У результаті сторінка завантажується повільніше, бо JS блокує HTML. Саме тому раніше скрипти радили класти в кінець <body>.
Що робить defer
<script src="app.js" defer></script>Тепер браузер:
- Починає завантажувати HTML і JS паралельно.
- Не блокує парсинг HTML.
- Виконує
app.jsтільки після того, як DOM повністю побудовано, але до подіїDOMContentLoaded.
Тобто послідовність така:
- HTML парситься,
- скрипт завантажується у фоні,
- DOM готовий,
- скрипт виконується,
- спрацьовує
DOMContentLoaded.
Приклад різниці
<!-- Без defer -->
<script src="slow.js"></script>
<p>Привіт!</p>
<!-- З defer -->
<script src="slow.js" defer></script>
<p>Привіт!</p>З defer текст «Привіт!» з'явиться одразу, поки скрипт ще вантажиться. Без defer сторінка «зависне», доки не завантажиться slow.js.
Коли скриптів кілька
<script src="a.js" defer></script>
<script src="b.js" defer></script>Скрипти з defer:
- завантажуються паралельно;
- але виконуються в порядку появи в HTML, тобто спочатку
a.js, потімb.js.
Це головна відмінність від async, де порядок не гарантується: першим виконається той файл, який першим доїхав по мережі. Тому для залежних одна від одної частин коду підходить лише defer.
Відмінність defer від async
| Атрибут | Коли завантажується | Коли виконується | Гарантований порядок | Типовий сценарій |
|---|---|---|---|---|
| (без атрибута) | Одразу при зустрічі | Одразу (блокує HTML) | Так | Старий JS у <head> |
| async | Паралельно з HTML | Одразу після завантаження (не чекає DOM) | Ні | Аналітика, реклама, віджети |
| defer | Паралельно з HTML | Після побудови DOM | Так | Основні скрипти застосунку |
Загальна таблиця:
| Властивість | Без атрибута | async | defer |
|---|---|---|---|
| Завантажує скрипт | Одразу (блокує HTML) | Паралельно | Паралельно |
| Виконує скрипт | Одразу після завантаження | Одразу після завантаження | Після розбору HTML |
| Порядок виконання | По порядку | Довільний | По порядку |
| Блокує HTML | Так | Ні | Ні |
| Чекає на DOM | Ні | Ні | Так |
Сумісність і вплив на DOMContentLoaded
defer працює в усіх сучасних браузерах. Якщо ставити скрипт у <head>, це найправильніший спосіб підключати JS:
<head>
<script src="main.js" defer></script>
</head>Браузер не блокує завантаження сторінки і гарантує, що ваш код виконається тоді, коли DOM уже готовий.
DOMContentLoaded спрацьовує після виконання всіх defer-скриптів. Тобто в обробнику цієї події можна бути впевненим, що ваші скрипти вже відпрацювали і зареєстрували все потрібне.
Окремо варто пам'ятати, що <script type="module"> поводиться як defer за замовчуванням, тому для модулів атрибут дописувати не потрібно.
Типові помилки
- Ставити
deferна інлайновий скрипт. Безsrcатрибут просто ігнорується, і код виконається негайно. - Очікувати від
asyncпорядку виконання. Якщоb.jsзалежить відa.js,asyncзламає його випадковим порядком; потрібенdefer. - Думати, що
deferвідкладає завантаження. Файл качається одразу і паралельно, відкладається лише виконання. - Класти в
deferкод, який має відпрацювати якнайраніше. Наприклад, антимерехтливий скрипт теми краще лишити синхронним інлайном. - Змішувати
deferзі скриптами, які роблятьdocument.write(). У відкладеному скрипті цей виклик просто ігнорується. - Все ще тримати скрипти в кінці
<body>, «бо так швидше». Зdeferу<head>браузер почне завантаження раніше і результат буде кращим.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.