Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое директивы defer и async в контексте загрузки JavaScript?
В современной веб-разработке defer и async — это булевы атрибуты тега <script>, которые управляют тем, как и когда браузер загружает и выполняет внешние JavaScript-файлы. Они критически важны для оптимизации производительности веб-страниц, так как позволяют избежать блокировки парсинга и отрисовки HTML.
Основная проблема: блокирующий парсинг
По умолчанию, когда браузер встречает тег <script> без атрибутов в процессе парсинга HTML, он приостанавливает построение DOM (Document Object Model), чтобы:
- Загрузить скрипт (если он внешний).
- Выполнить его содержимое.
Только после этого парсинг HTML возобновляется. Для тяжелых скриптов или медленных сетей это приводит к долгой задержке перед отображением страницы (плохие метрики FCP — First Contentful Paint и LCP — Largest Contentful Paint).
<!-- Классический блокирующий скрипт. Парсинг HTML остановится здесь -->
<script src="heavy-script.js"></script>
Решение: атрибуты async и defer
Оба атрибута заставляют браузер загружать скрипт асинхронно, не останавливая парсинг HTML. Однако они кардинально различаются моментом выполнения.
Атрибут async
Скрипт с async выполняется как только загрузится, при этом парсинг HTML приостанавливается на время его выполнения. Порядок выполнения не гарантирован — какой скрипт загрузится первым, тот и запустится.
<script async src="analytics.js"></script>
<script async src="widget.js"></script>
<!-- widget.js может выполниться ДО analytics.js, если загрузится быстрее -->
Идеальное применение: для абсолютно независимых скриптов, например, аналитики, рекламных баннеров, виджетов соцсетей, которые не зависят от DOM или других скриптов.
Атрибут defer — подробный разбор
defer — это более предсказуемый и безопасный вариант. Он указывает браузеру:
- Загружать скрипт немедленно, в фоновом режиме, параллельно с парсингом HTML.
- Отложить выполнение скрипта до того момента, когда:
* Весь HTML-документ будет полностью распарсен (**DOM готов**).
* Событие `DOMContentLoaded` вот-вот произойдет.
Ключевое преимущество defer: скрипты выполняются строго в том порядке, в котором они объявлены в HTML, независимо от времени загрузки.
<!-- Парсинг HTML НЕ блокируется. Скрипты загружаются параллельно. -->
<script defer src="vendor-library.js"></script>
<script defer src="app-logic.js"></script>
<script defer src="ui-components.js"></script>
<!-- Гарантированный порядок выполнения: 1. library.js, 2. app-logic.js, 3. ui-components.js -->
Сравнительная таблица async vs defer
| Критерий | Без атрибутов (обычный) | async | defer |
|---|---|---|---|
| Загрузка (парсинг HTML) | Блокируется | Не блокируется (параллельная) | Не блокируется (параллельная) |
| Выполнение (относительно парсинга) | Сразу после загрузки, парсинг приостанавливается | Сразу после загрузки, парсинг приостанавливается | После завершения парсинга HTML, перед DOMContentLoaded |
| Гарантия порядка выполнения | Да (по очереди загрузки) | Нет (кто раньше загрузился) | Да (как указано в разметке) |
| Зависимость от готовности DOM | Нет (DOM может быть не готов) | Нет (DOM может быть не готов) | Да (DOM гарантированно готов) |
Почему defer часто является лучшим выбором?
- Предсказуемость: Порядок выполнения критически важен, когда скрипты зависят друг от друга (например, библиотека -> плагин -> ваш код).
- Безопасность DOM: Поскольку выполнение происходит после построения DOM, вам не нужны обертки вроде
DOMContentLoadedдля доступа к элементам страницы. - Производительность: Максимально ускоряет отрисовку контента, полностью убирая блокировку парсинга.
Практический пример и современный подход
В современном workflow, особенно при использовании сборщиков (Webpack, Vite), скрипты часто вставляются в <head> с атрибутом defer. Это стало лучшей практикой.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Современная страница</title>
<!-- Скрипты в head с defer — это нормально и правильно! -->
<script defer src="bundle.js"></script>
</head>
<body>
<h1>Контент загружается и отрисовывается без задержек</h1>
<!-- К моменту выполнения bundle.js весь этот DOM уже будет готов -->
</body>
</html>
Важное замечание: Атрибут defer работает только для внешних скриптов (с атрибутом src). Для inline-скриптов он не имеет эффекта.
Итог
Используйте defer для большинства скриптов, которые взаимодействуют с DOM или зависят друг от друга. Используйте async для полностью изолированных сторонних скриптов, где порядок и момент выполнения не важны. Отказ от блокирующих скриптов — один из самых простых и эффективных шагов в оптимизации скорости загрузки веб-приложения.