Скрипты async, defer и оптимизация
Уважаемые начинающие и продолжающие JavaScript-разработчики, давайте прокачаем ваши скиллы и научимся увеличивать скорость загрузки ваших проектов. Для этого мы познакомим вас с базой — скриптами async и defer, а после подскажем еще несколько способов оптимизации процесса загрузки веб-страниц.
Скорость загрузки веб-страницы влияет на удовлетворенность пользователей, конверсию и ранжирование в поисковых системах. Одним из факторов, которые могут замедлить загрузку, является подключение внешних скриптов, таких как библиотеки JavaScript, аналитика и реклама.
По умолчанию браузеры загружают и выполняют скрипты синхронно, то есть один за другим, блокируя разбор HTML-документа — это приводит к долгому ожиданию отображения содержимого страницы и плохому пользовательскому опыту. К счастью, существуют способы оптимизировать процесс загрузки скриптов с помощью атрибутов async и defer.
Атрибуты async и defer
Атрибуты async и defer являются логическими атрибутами, которые добавляются к тегу <script>, чтобы указать браузеру, как загружать и выполнять скрипт. Они применимы только к внешним скриптам, то есть тем, у которых есть атрибут src.
<script src="script.js"></script> <!-- Обычный скрипт -->
<script async src="script.js"></script> <!-- Асинхронный скрипт -->
<script defer src="script.js"></script> <!-- Отложенный скрипт -->
Приоритет загрузки
- Обычные скрипты имеют высокий приоритет и блокируют загрузку других ресурсов до своего завершения.
- Асинхронные скрипты имеют низкий приоритет и не блокируют загрузку других ресурсов.
- Отложенные скрипты имеют средний приоритет и не блокируют загрузку других ресурсов, но гарантируют свою загрузку до события DOMContentLoaded.
Атрибуты async и defer влияют на приоритет загрузки скрипта относительно других ресурсов на странице.
Понятие defer
Атрибут defer указывает браузеру отложить выполнение скрипта до тех пор, пока не будет завершен разбор HTML-документа. Это позволяет браузеру продолжать отображать содержимое страницы без задержек из-за ожидания скрипта. Скрипты с этим атрибутом сохраняют порядок своего появления на странице и выполняются перед событием DOMContentLoaded.
Понятие async
Атрибут async указывает браузеру загружать скрипт асинхронно, то есть параллельно с разбором HTML-документа. Это позволяет браузеру не ждать загрузки скрипта и продолжать разбор HTML. Скрипты с этим атрибутом выполняются сразу после загрузки, без учета порядка появления на странице или завершения разбора HTML.
Могут ли async и defer использоваться в одном файле? — Нет, нельзя использовать оба атрибута в одном файле. Если указать оба атрибута для одного скрипта, то будет применяться только атрибут async. Это означает, что скрипт будет загружаться асинхронно и выполняться сразу после загрузки, без учета порядка появления на странице или завершения разбора HTML.
Разница между async и defer
Главная разница между async и defer заключается в том, когда они выполняют скрипты:
- async выполняет скрипты сразу после загрузки
- defer — после завершения разбора HTML.
Это влияет на то, как скрипты взаимодействуют с DOM и другими скриптами на странице, поэтому:
- Если порядок выполнения скриптов важен, то лучше использовать атрибут defer, так как он гарантирует, что скрипты будут выполняться в том же порядке, в котором они появляются на странице.
- Если же порядок выполнения скриптов не важен, то можно использовать атрибут async, так как он может ускорить загрузку страницы за счет параллельной загрузки и выполнения скриптов.
Когда используется режим async
Режим async используется для скриптов, которые не требуют немедленного выполнения, не влияют на отображение страницы и не зависят от других скриптов. Такие скрипты могут быть загружены и выполнены асинхронно, то есть параллельно с разбором HTML-документа, что позволяет браузеру не ждать загрузки скрипта и продолжать отображать содержимое страницы без задержек. Это могут быть скрипты, которые:
- Отправляют данные на сервер или получают данные с сервера без блокировки пользовательского интерфейса (для отправки формы, получения данных по API или обновления части страницы).
- Загружают рекламу или аналитику на страницу (для Google Analytics, Facebook Pixel или Yandex.Metrica).
- Добавляют дополнительную функциональность на страницу без вмешательства в основное содержимое (для добавления кнопок социальных сетей, виджетов чата или опросов).
Когда используется режим defer
Режим defer используется для скриптов, которые требуют выполнения после разбора HTML-документа и может использоваться как для скриптов, которые не зависят от других скриптов, так и для скриптов, которые зависят от других скриптов.
* В первом случае порядок выполнения скриптов не имеет значения, а во втором случае порядок выполнения скриптов имеет значение и должен быть контролируемым.Атрибут defer подходит для скриптов, которые работают с элементами DOM, добавляют обработчики событий или инициализируют некоторую функциональность. Например, это могут быть скрипты, которые:
- Добавляют слайдер, карусель, галерею или другие интерактивные элементы на страницу
- Добавляют валидацию, маски или другие улучшения для форм на странице
- Добавляют динамическое меню, выпадающий список или другие элементы навигации на странице
- Добавляют анимацию, эффекты или другие визуальные улучшения на странице.
В этом примере мы:
- Подключаем jQuery и плагин для слайдера с атрибутом defer, чтобы они загрузились после разбора HTML-документа и выполнились в том же порядке, в котором они появляются на странице.
- Инициализируем слайдер с помощью jQuery в теге <script> с атрибутом defer, чтобы он выполнился после загрузки и выполнения всех предыдущих скриптов. Так мы гарантируем, что слайдер будет работать корректно и не будет блокировать отображение страницы.
Динамически загружаемые скрипты
Иногда бывает необходимо загрузить и выполнить скрипт динамически, то есть в процессе работы страницы, а не заранее. Это бывает полезно для реализации ленивой загрузки (lazy loading), когда скрипт загружается только тогда, когда он действительно нужен, или для подгрузки дополнительного функционала по запросу пользователя.
Для того, чтобы динамически загрузить скрипт, нужно создать элемент <script> с помощью метода document.createElement и добавить его в документ с помощью метода document.body.append. Важно указать атрибут src с адресом скрипта, который нужно загрузить:
// Создаем элемент <script>
let script = document.createElement("script");
// Указываем атрибут src с адресом скрипта
script.src = "script.js";
// Добавляем элемент <script> в документ
document.body.append(script);
По умолчанию динамически созданные скрипты загружаются и выполняются асинхронно, то есть не блокируют разбор HTML и не сохраняют порядок своего появления. Это поведение можно изменить с помощью атрибутов async и defer, которые работают так же, как и для обычных скриптов. Например, если мы хотим загрузить и выполнить скрипт синхронно, то нужно указать атрибут async со значением false:
// Создаем элемент <script>
let script = document.createElement("script");
// Указываем атрибут src с адресом скрипта
script.src = "script.js";
// Указываем атрибут async со значением false
script.async = false;
// Добавляем элемент <script> в документ
document.body.append(script);
Если мы хотим отложить выполнение скрипта до завершения разбора HTML, то нужно указать атрибут defer со значением true:
// Создаем элемент <script>
let script = document.createElement("script");
// Указываем атрибут src с адресом скрипта
script.src = "script.js";
// Указываем атрибут defer со значением true
script.defer = true;
// Добавляем элемент <script> в документ
document.body.append(script);
Для того, чтобы отследить момент загрузки и выполнения динамического скрипта, можно использовать обработчики событий load и error. Событие load возникает, когда скрипт успешно загружен и выполнен. Событие error возникает, когда произошла ошибка при загрузке или выполнении скрипта:
// Создаем элемент <script>
let script = document.createElement("script");
// Указываем атрибут src с адресом скрипта
script.src = "script.js";
// Добавляем обработчик события load
script.onload = function() {
// Скрипт успешно загружен и выполнен
console.log("Скрипт загружен");
};
// Добавляем обработчик события error
script.onerror = function() {
// Произошла ошибка при загрузке или при выполнении скрипта
console.log("Ошибка при загрузке скрипта");
};
// Добавляем элемент <script> в документ
document.body.append(script);
Как оптимизировать процесс загрузки страницы
Процесс загрузки веб-страницы зависит от многих факторов, таких как размер и количество ресурсов, тип и скорость интернет-соединения, производительность сервера и клиента, настройки кэширования и сжатия. Оптимизация процесса загрузки позволит улучшить пользовательский опыт, повысить конверсию и ранжирование в поисковых системах. Существует множество способов оптимизации, но в этой статье мы рассмотрим только те из них, что связаны с использованием скриптов:
- Использовать атрибуты async и defer. Это позволит браузеру продолжать разбор HTML-документа без блокировки из-за ожидания скрипта.
- Минимизировать размер и количество скриптов которые нужно загрузить на странице. Достигается это при помощи инструментов для сжатия, объединения и удаления лишнего кода из скриптов (UglifyJS , Webpack или Gulp).
- Ускорить загрузку скриптов. Этого можно достичь за счет более близкого расположения данных к пользователю и использования оптимальных протоколов передачи данных при помощи CDN — сети доставки контента.
- Осуществлять динамическую загрузку скриптов по необходимости, а не заранее. Для этого существует метод document.createElement для создания элемента <script> и добавления его в документ с указанием атрибута src с адресом скрипта. Также можно использовать методы import() или require() для загрузки модулей JavaScript в зависимости от условий. Это позволит уменьшить объем данных, которые нужно загрузить при первом открытии страницы, и увеличить производительность при последующих запросах.
- Использовать сервис-воркеры (service workers) для кэширования и обслуживания скриптов в оффлайн-режиме. Это позволит ускорить загрузку страницы, уменьшить трафик и повысить надежность при плохом или отсутствующем интернет-соединении.