Async и Defer — стратегии загрузки JavaScript


JavaScript является неотъемлемой частью любого современного веб-приложения, и стратегии, которые мы решаем использовать для загрузки, напрямую влияют на производительность работы этого самого приложения. В данной статье мы попробуем понять важные различия между каждым подходом, плюсы и минусы наряду с последствиями производительности и способы оптимизации по взаимодействию со страницей и времени загрузки.

Для демонстрации я создам веб-сайт, состоящий из следующих внешних зависимостей. Обратите особое внимание на соответствующие размеры файлов, так как время загрузки файлов прямо пропорционально этому.

  • HTML - страница ~ 1 МБ. Содержит фактическую разметку/содержимое, чтобы показать некоторые динамические выходные данные из JavaScript.
  • Изображение - image1.png ~ 5 Мб
  • JavaScript - file1.JS ~ 3 МБ - это ядро (основной файл) javascript и зависит от синтаксического анализа HTML. Он нужен для того, чтобы показать некоторые динамическое содержимое или смонтировать react/angular компонент на странице.
  • JavaScript - file2.js ~ 460B - небольшой, независимый файл javascript, который взаимодействует с dom.
  • JavaScript - file3.js ~ 1.5 MB - это вторичный файл js и зависит от file1.js, чтобы выполнить некоторый, обладающий более низким приоритетом код. Этот код не требуется сразу для рендеринга страницы и взаимодействия с пользователем; он показывает иконки социальных сетей, комментарии, онлайн помощь, запуск некоторых задач аналитики и т.д.
Теперь пришло время проанализировать различные подходы

Подход-1 [скрипты в разделе head]

В первом случае мы загрузим все теги scripts в раздел head нашего HTML. Ниже приведен скриншот анализа сетевой вкладки chrome страницы, к готовой для взаимодействия с пользователем.



Плюсы:

Последовательность выполнения кода различных JS-файлов будет сохранена в том порядке, в котором файлы были включены в HTML. В текущем примере, даже если file2 и file3 были загружены до file1, порядок выполнения будет правильным.

Минусы:

В этом сценарии синтаксический анализ HTML будет приостановлен до тех пор, пока все 3 скрипта в разделе head не будут загружены, проанализированы и выполнены. Пустой белый экран будет показан пользователю, даже если HTML-файл уже был загружен [но не проанализирован]. Это, безусловно, не есть хорошо для юзабилити.

Ни один из вышеперечисленных скриптов не сможет получить доступ / манипулировать HTML-страницей, так как DOM еще не готов. Одним из возможных решений для обработки этой проблемы является прослушивание события DOMContentLoaded, а затем выполнение кода после этого. DOMContentLoadedСобытие срабатывает, когда исходный HTML-документ был полностью загружен и проанализирован, не дожидаясь завершения загрузки таблиц стилей, изображений.

Подход-2 [скрипты в конце]

Чтобы преодолеть 2 проблемы, с которыми мы сталкиваемся в первом подходе, давайте загрузим все 3 скрипта в нижней части тега body.



Плюсы: HTML анализируется перед загрузкой скриптов, так что пользователь будет иметь возможность видеть фактическое содержание сразу вместо того, чтобы ждать скриптов.

Так как все скрипты выполняются после разбора HTML, то все они могут получить доступ к DOM для любых манипуляций. Последовательность выполнения скриптов сохраняется.

Минусы:

Нет прироста производительности как такового.

Подход-3 [использование атрибута Async]

<script src="/javasctipt-file-path" asynс></script>
HTML5 представил async атрибут script, который помогает в загрузке соответствующих файлов скрипта параллельно на другой поток, не влияя на синтаксический анализ HTML.

Тем не менее, соответствующий сценарий будет проанализирован и выполнен, как только он завершит загрузку, независимо от того, завершен ли или нет синтаксический анализ HTML, и будет иметь ссылку на элемент DOM до этой конкретной точки.



Здесь вы можете четко увидеть, что file2.js был загружен до HTML файла. Однако, в то время как браузер загружает file2, он не приостановил синтаксический анализ HTML и из — за этого, ко времени его выполнения-он имел ссылку на заполнитель html, чтобы ввести динамическое содержимое.

Плюсы: Поскольку скрипты загружаются в другом потоке, синтаксический анализ HTML не будет приостановлен, и пользователь сможет видеть непосредственный контент вместо белого пустого экрана. Основной прирост производительности, т. е. DOMContentLoaded время уменьшилось с 47.68 секунд до всего 21.12 секунд и составляет ~ 55% прироста .

Минусы:

Последовательность выполнения JS не сохраняется. Он выполняется в соответствующем порядке загрузки, а не включенной последовательности скриптов внутри HTML. Поддержка браузера - не поддерживается на старых веб-браузерах, т. е. IE 9 и ниже.

Что произойдет, если JS загружается до того, как DOM элемент будет доступен?Будет выброшена выброшена ошибка.

Примечание: размещение скриптов с атрибутом async в нижней части раздела body будет бесполезным и эквивалентным подходу-2.

Подход-4 [использование атрибута Defer]

Defer атрибут заставит скрипт выполниться только после того как парсинг HTML был завершен. Один очень важный момент, который следует учитывать здесь, заключается в том, что Chrome еще не поддерживает отсрочку и не оказывает влияния на продолжительность DOMContentLoaded. Однако он выполняет скрипты в конце синтаксического анализа HTML.

Плюсы:

Последовательность импорта скриптов сохраняется. Итак, file3.js выполняется только после завершения загрузки и выполнения file1, даже если file3 был загружен ранее.

Поддержка браузеров- он имеет лучшую поддержку браузеров по сравнению с атрибутом asynс, т. е. частично поддерживается в IE v6-9

Скрипты могут получить доступ к DOM, так как он выполняется только после разбора полного HTML.

Минусы:

Первоначально я думал, что отсрочка будет лучшим выбором, чем асинхронность, но позже обнаружил, что Chrome еще не поддерживает его [версия 71.0.3578.98] и не оказывает влияния на продолжительность DOMContentLoaded.

Тем не менее, он работает так, как ожидалось, в Firefox со значительным улучшением производительности.

Выводы

Предпочтительнее размещать теги скриптов в разделе head с атрибутом async для сторонних библиотек, которые зависят от Google Analytics, Google reCAPTCHA или чего-либо еще, что не требует DOM-доступа, поскольку соответствующие скрипты загружаются параллельно, но выполняются немедленно.

Используйте defer для всех других скриптов, загруженных в разделе head, поскольку они также будут загружаться параллельно, но будут выполняться только после завершения синтаксического анализа HTML и DOM готов к доступу/манипуляции.

Вы также можете использовать сочетание DOMContentLoaded listener внутри асинхронных скриптов для выполнения функциональности позже. Пожалуйста, оставьте свои мнения и выводы в комментариях, и я буду рад обсудить их с вами.
Автор этого материала - я - Пахолков Юрий. Я оказываю услуги по написанию программ на языках Java, C++, C# (а также консультирую по ним) и созданию сайтов. Работаю с сайтами на CMS OpenCart, WordPress, ModX и самописными. Кроме этого, работаю напрямую с JavaScript, PHP, CSS, HTML - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.

тегистатьи IT, javascript, создание сайтов




Отправляя сообщение я подтверждаю, что ознакомлен и согласен с политикой конфиденциальности данного сайта.



С. Визгорев - AI Factory's Chess, уровень 8, 18 октября 2015
C#: объединение текстовых файлов с одинаковыми именами из разных папок
Создание плагина для WordPress. Часть 2: меню и вывод из базы данных