Скрипты: async, defer
Атрибуты async и defer позволяют загружать JavaScript в фоне, не блокируя разбор HTML и ускоряя отображение страницы.
То, где вы размещаете <script> и как вы его загружаете, напрямую влияет на скорость появления страницы. По умолчанию скрипты могут остановить браузер в середине рендеринга; атрибуты async и defer позволяют сообщить браузеру, что JavaScript нужно скачать в фоне. В этой статье объясняется, как ведут себя обычные блокирующие скрипты, что меняют async и defer, почему модульные скрипты откладываются автоматически, и как выбрать подходящий вариант для каждого скрипта.
Как обычный скрипт блокирует разбор
<script> без async или defer блокирует рендеринг. Когда HTML-парсер встречает этот тег, он прекращает строить страницу, загружает скрипт (если указан атрибут src), выполняет его до конца и только затем возобновляет разбор остального документа:
<p>...content before script...</p>
<script src="big.js"></script> <!-- parsing pauses here until big.js downloads AND runs -->
<p>...content after script (the user can't see this yet)...</p>Из этого следует два последствия:
- Скрипт не видит элементы DOM, которые расположены после него, поскольку они ещё не разобраны.
- Пока скрипт загружается, пользователь видит лишь частично построенную страницу. На медленном соединении это классическая проблема «белой страницы».
Исторически её решали, помещая скрипты в самый конец <body>. async и defer дают более чистое решение: оставьте тег в <head>, но запретите ему блокировать загрузку.
Оба атрибута
asyncиdeferдействуют только на внешние скрипты — им необходим атрибутsrc. На встроенных блоках<script>они игнорируются.
Понимание атрибута async
Что такое атрибут async?
Когда вы добавляете атрибут async к тегу <script>, браузеру даётся команда скачать скрипт в фоне, не блокируя разбор HTML. Как только загрузка завершается, браузер приостанавливает разбор, выполняет скрипт и продолжает. Поскольку время загрузки варьируется, скрипты с async запускаются как только готовы — в непредсказуемом порядке, возможно до того, как остальной документ разобран.
Это делает async идеальным для независимых скриптов, которые не зависят от DOM или друг от друга: аналитика, реклама и прочие трекеры, работающие по принципу «выстрелил и забыл».
Пример кода: использование async
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Async Example</title>
</head>
<body>
<h1>Testing Async</h1>
<script async src="https://example.com/async-script.js"></script>
</body>
</html>Преимущества и компромиссы async
- Без блокировки: разбор HTML продолжается во время загрузки скрипта.
- Запускается немедленно: скрипт выполняется сразу по завершении загрузки — хорошо для независимых скриптов.
- Без гарантированного порядка: при наличии нескольких скриптов с
asyncпервым запускается тот, что загрузится быстрее. Никогда не используйтеasyncдля скриптов, зависящих друг от друга. - Может запуститься до готовности DOM: скрипт может выполниться до окончания разбора, поэтому не предполагайте наличие последующих элементов.
Использование атрибута defer
Что такое атрибут defer?
Атрибут defer тоже загружает скрипт в фоне без блокировки разбора. Отличие — в моменте выполнения: отложенный скрипт запускается только после полного разбора HTML-документа, прямо перед тем как срабатывает событие DOMContentLoaded. Отложенные скрипты также сохраняют порядок в документе — первый тег с defer всегда выполняется раньше второго, независимо от того, какой загрузился первым.
Это делает defer правильным выбором по умолчанию для кода приложения: весь DOM гарантированно существует, а зависимые скрипты выполняются по порядку.
Пример кода: использование defer
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Defer Example</title>
</head>
<body>
<h1>Testing Defer</h1>
<script defer src="https://example.com/defer-script.js"></script>
</body>
</html>Преимущества использования defer
- DOM готов: весь HTML-документ разобран до запуска скрипта.
- Порядок сохраняется: скрипты выполняются в порядке их расположения в документе, что критично для скриптов, зависящих друг от друга.
Модульные скрипты откладываются по умолчанию
<script type="module"> ведёт себя как отложенный скрипт автоматически — добавлять defer не нужно. Он загружается без блокировки разбора и выполняется после разбора документа, по порядку. Чтобы модуль запустился как только он готов (поведение async), добавьте async явно:
<!-- Deferred automatically; runs after parsing, in order -->
<script type="module" src="app.js"></script>
<!-- Opt into async: runs as soon as it (and its imports) are ready -->
<script type="module" async src="tracker.js"></script>Если вы только знакомитесь с модулями, см. Введение в модули.
async и defer в сравнении
| Поведение | async | defer |
|---|---|---|
| Блокирует разбор HTML? | Нет | Нет |
| Когда выполняется? | Сразу после загрузки | После разбора, до DOMContentLoaded |
| Порядок выполнения | Кто загрузился первым | Порядок в документе, гарантировано |
| DOM полностью доступен? | Не гарантировано | Да |
| Лучше всего для | Независимых скриптов (аналитика, реклама) | Кода приложения, зависимых скриптов |
Простое правило:
- Используйте
async, когда скрипт самодостаточен — не зависит от других скриптов или от DOM. - Используйте
defer, когда скрипту нужен весь DOM или важен порядок выполнения.
Практический пример: решение о загрузке скриптов
Рассмотрим загрузку утилитарной библиотеки (например, Lodash) и вашего файла, который от неё зависит. Поскольку порядок важен, defer — правильный выбор: оба скрипта скачиваются в фоне, но выполняются последовательно после разбора:
<script defer src="https://cdn.jsdelivr.net/npm/lodash/lodash.min.js"></script>
<script defer src="script.js"></script>Здесь lodash.min.js гарантированно выполнится раньше script.js, и оба дождутся разбора страницы. Если заменить их на async, есть риск, что script.js запустится первым и завершится с ошибкой, потому что Lodash ещё не загружен.
Для загрузки не-скриптовых ресурсов (изображений, стилей) и обработки их успешной загрузки или ошибки см. Загрузка ресурсов: onload и onerror.
Заключение
Грамотное использование атрибутов async и defer в тегах скриптов критически важно для современной веб-разработки. Понимая и правильно применяя эти атрибуты, разработчики могут обеспечить более быструю загрузку страниц и лучший пользовательский опыт. Асинхронная загрузка скриптов — это оптимизация производительности и создание эффективных веб-приложений, ориентированных на пользователя.