Чем отличаются defer и async
Внешние скрипты, порядок исполнения
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/script-async-defer.
Если JavaScript-кода много – его выносят в отдельный файл, который подключается в HTML:
Здесь /path/to/script.js – это абсолютный путь к файлу, содержащему скрипт (из корня сайта).
Браузер сам скачает скрипт и выполнит.
Можно указать и полный URL, например:
Вы также можете использовать путь относительно текущей страницы. Например, src=»https://learn.javascript.ru/lodash.js» обозначает файл из текущей директории.
Чтобы подключить несколько скриптов, используйте несколько тегов:
Как правило, в HTML пишут только самые простые скрипты, а сложные выносят в отдельный файл.
Браузер скачает его только первый раз и в дальнейшем, при правильной настройке сервера, будет брать из своего кеша.
Благодаря этому один и тот же большой скрипт, содержащий, к примеру, библиотеку функций, может использоваться на разных страницах без полной перезагрузки с сервера.
В одном теге SCRIPT нельзя одновременно подключить внешний скрипт и указать код.
Вот так не сработает:
Асинхронные скрипты: defer/async
Браузер загружает и отображает HTML постепенно. Особенно это заметно при медленном интернет-соединении: браузер не ждёт, пока страница загрузится целиком, а показывает ту часть, которую успел загрузить.
В чем отличие атрибутов «defer/async» и использования «Ajax»?
Здравствуйте! Подскажите разницу между использованием атрибутов «defer/async» при загрузке кода, и использованием AJAX? На странице «Хабрахабра» объясняется разница, что
При использовании AJAX нет необходимости обновлять каждый раз всю страницу, так как обновляется только ее конкретная часть. Это намного удобнее, так как не приходится долго ждать, и экономичнее, так как не все обладают безлимитным интернетом. Правда в этом случае, разработчику необходимо следить, чтобы пользователь был в курсе того, что происходит на странице. Это можно реализовать с использованием индикаторов загрузки, текстовых сообщений о том, что идёт обмен данными с сервером. Необходимо также понимать, что не все браузеры поддерживают AJAX (старые версии браузеров и текстовые браузеры). Плюс Javascript может быть отключен пользователем. Поэтому, не следует злоупотреблять использованием технологии и прибегать к альтернативным методам представления информации на Web-сайте.
Обобщим достоинства AJAX: Возможность создания удобного Web-интерфейса Активное взаимодействие с пользователем Частичная перезагрузка страницы, вместо полной Удобство использования
AJAX использует два метода работы с веб-страницей: изменение Web-страницы не перезагружая её, и динамическое обращение к серверу.
Объяснение от javascript.ru
Кардинально решить эту проблему помогут атрибуты async или defer:
Атрибут async Поддерживается всеми браузерами, кроме IE9-. Скрипт выполняется полностью асинхронно. То есть, при обнаружении браузер не останавливает обработку страницы, а спокойно работает дальше. Когда скрипт будет загружен – он выполнится.
Атрибут defer Поддерживается всеми браузерами, включая самые старые IE. Скрипт также выполняется асинхронно, не заставляет ждать страницу, но есть два отличия от async.
Первое – браузер гарантирует, что относительный порядок скриптов с defer будет сохранён.
То есть, в таком коде (с async) первым сработает тот скрипт, который раньше загрузится: Javascript.ru
Вопрос: 1. В чем состоит принципиальная разница в скорости загрузки? 2. Чем предпочтительней пользоваться при: а)создании сайта-страницы? б)создании сайта-магазина?
Скрипты: async, defer
В современных сайтах скрипты обычно «тяжелее», чем HTML: они весят больше, дольше обрабатываются.
Это ведёт к двум важным проблемам:
Конечно, есть пути, как это обойти. Например, мы можем поместить скрипт внизу страницы. Тогда он сможет видеть элементы над ним и не будет препятствовать отображению содержимого страницы:
Но это решение далеко от идеального. Например, браузер замечает скрипт (и может начать загружать его) только после того, как он полностью загрузил HTML-документ. В случае с длинными HTML-страницами это может создать заметную задержку.
Такие вещи незаметны людям, у кого очень быстрое соединение, но много кто в мире имеет медленное подключение к интернету или использует не такой хороший мобильный интернет.
К счастью, есть два атрибута тега
. содержимое после скрипта.
Следующий пример это показывает:
Отложенные с помощью defer скрипты сохраняют порядок относительно друг друга, как и обычные скрипты.
Поэтому, если сначала загружается большой скрипт, а затем меньшего размера, то последний будет ждать.
Браузеры сканируют страницу на предмет скриптов и загружают их параллельно в целях увеличения производительности. Поэтому и в примере выше оба скрипта скачиваются параллельно. small.js скорее всего загрузится первым.
Атрибут defer будет проигнорирован, если в теге
. содержимое после скриптов.
Асинхронные скрипты очень полезны для добавления на страницу сторонних скриптов: счётчиков, рекламы и т.д. Они не зависят от наших скриптов, и мы тоже не должны ждать их:
Динамически загружаемые скрипты
Мы можем также добавить скрипт и динамически, с помощью JavaScript:
Динамически загружаемые скрипты по умолчанию ведут себя как «async».
Мы можем изменить относительный порядок скриптов с «первый загрузился – первый выполнился» на порядок, в котором они идут в документе (как в обычных скриптах) с помощью явной установки свойства async в false :
Например, здесь мы добавляем два скрипта. Без script.async=false они запускались бы в порядке загрузки ( small.js скорее всего запустился бы раньше). Но с этим флагом порядок будет как в документе:
Итого
У async и defer есть кое-что общее: они не блокируют отрисовку страницы. Так что пользователь может просмотреть содержимое страницы и ознакомиться с ней сразу же.
Но есть и значимые различия:
Пользователь может знакомиться с содержимым страницы, читать её, но графические компоненты пока отключены.
Поэтому обязательно должна быть индикация загрузки, нерабочие кнопки – отключены с помощью CSS или другим образом. Чтобы пользователь явно видел, что уже готово, а что пока нет.
На практике defer используется для скриптов, которым требуется доступ ко всему DOM и/или важен их относительный порядок выполнения.
А async хорош для независимых скриптов, например счётчиков и рекламы, относительный порядок выполнения которых не играет роли.
Как быстрее DOM построить: парсинг, async, defer и preload
На сегодняшний день, джентльменский набор по ускорению сайта включает в себя всё от минификации и оптимизации файлов до кеширования, CDN, разделения кода и так называемого tree shaking. Но даже если вы не знакомы с этой терминологией, значительного ускорения можно добиться и парой ключевых слов с продуманной структурой кода.
По кирпичикам
HTML описывает структуру страницы. Для того, чтобы браузер смог извлечь из HTML хоть какую то пользу, его надо конвертировать в понятный браузерам формат — Document Object Model или попросту DOM. У браузера есть специальная функция парсер, позволяющая конвертировать из одного формата в другой. HTML парсер конвертирует HTML в DOM.
Связи различных элементов в HTML определяются вложенностью тегов. В DOM, эти же связи образуют древовидную структуру данных. У каждого тега HTML в DOM есть своя вершина (вершина DOM).
Шаг за шагом браузер строит DOM. Как только первые строки кода становятся доступными, браузер начинает парсить HTML, добавляя вершины в дерево.
У DOM две роли: объектная репрезентация HTML документа и в то же время DOM служит интерфейсом, связывая страницу с внешним миром, например с JavaScript. Если, например, вызвать document.getElementById() то функция вернёт вершину DOM. Для манипуляции с вершиной и тем как её видит пользователь у вершины есть множество функций.
CSS стили на странице отображаются в модель CSSOM — CSS Object Model. Очень похож на DOM, но для CSS, а не HTML. В отличии от DOM, CSSOM нельзя построить пошагово, т.к. стили в CSS могут переопределять друг друга. Браузеру приходится значительно потрудится чтобы применить CSS к DOM.
История тега <script>
Раньше, чтобы запустить скрипт, нужно было приостановить парсинг и продолжить его только после выполнения скрипта JavaScript’ом.
Скрипты также могут отправлять запросы в DOM и если это происходит во время постройки DOM, результат может оказаться непредсказуемым.
document.write() — функция-наследие, которая может сломать страницу самым неожиданным образом, поэтому лучше её не использовать, даже если браузеры будут её поддерживать. По этим причинам в браузерах были разработаны хитрые методы обхода проблем с производительностью, вызванных блокированием скриптов, о них чуть ниже.
А что с CSS?
JavaScript приостанавливает парсинг HTML, т.к. скрипты могут менять страницу. CSS не может изменить страницу, поэтому причин останавливать процесс парсинга у него нет, так?
Из-за этого CSS может блокировать парсинг в зависимости от порядка подключения скриптов и стилей на странице. Если внешние таблицы стилей находятся до скриптов, то создание DOM и CSSOM может мешать друг другу. Когда парсер доходит до скрипта, постройка DOM не может продолжаться до тех пор, пока JavaScript не выполнит скрипт, а JavaScript в свою очередь не может быть запущен, пока CSS не скачается, распарится и CSSOM станет доступным.
Ещё один момент, о котором не стоит забывать. Даже если CSS не будет блокировать постройку DOM, он блокирует процесс рендеринга. Браузер ничего не покажет пока у него не будет готовых DOM и CSSOM. Это потому, что зачастую страницы без CSS непригодны для использования. Если браузер покажет кривую страницу без CSS, а потом через мгновение полную стилизованную страницу, то у пользователя возникнет когнитивный диссонанс.
У этого явления есть название — Проблеск Неоформленного Содержания, сокращённо ПнОС [Flash of Unstyled Content или FOUC]
Во избежание таких проблем нужно как можно быстрее предоставить CSS. Помните золотое правило «стили сверху, скрипты снизу»? Теперь вы в курсе почему это важно!
Назад в будущее. Спекулятивный парсинг
Приостанавливание парсера при каждой встрече со скриптом будет означать задержку в обработке остальных данных, подгружаемых в HTML.
Раньше, если взять несколько скриптов и изображений, подгрузив их, например, так:
Процесс парсинга выглядел бы как показано ниже:
Такое поведение изменилось в 2008 годах, когда IE представил так называемый «опережающий загрузчик» [the lookahead downloader]. С помощью него файлы подгружались в фоновом режиме прямо во время выполнения скриптов. Вскоре этот метод переняли и Firefox, Chrome с Safari, используя его под разными названиями, равно как и большинство современных браузеров. У Chrome с Safari это «предварительный анализ» [the preload scanner], а у Firefox — спекулятивный парсер. Идея вот в чём, с учётом того, что постройка DOM во время выполнения скриптов весьма рискованна, можно всё ещё парсить HTML чтобы посмотреть какие ресурсы должны быть подгружены. Затем обнаруженные файлы добавляются в очередь на загрузку и скачиваются параллельно в фоновом режиме. И к моменту завершения скрипта, необходимые файлы могут быть уже готовы к использованию.
Таким образом с применением такого метода, процесс парсинга выше, выглядел бы так:
Такой процесс называется «спекулятивным», иногда «рискованным», потому что HTML всё ещё может меняться во время выполнения скрипта (напомню про document.write ), что может привести к работе проделанной впустую. Но несмотря что такой сценарий возможен, он крайне редок, именно поэтому спекулятивный парсинг даёт огромный прирост производительности.
В то время как другие браузеры загружают таким способом только привязанные файлы, парсер Firefox ещё и продолжает строить DOM во время выполнения скриптов. Плюс этого в том что если спекуляция прошла, то часть работы в постройке DOM уже будет проделана. А вот в случае неудачной спекуляции работы будет затрачено больше.
(Пред)загрузка
При использовании такой техники загрузки можно значительно повысить скорость загрузки и никаких специальных навыков для этого не понадобится. Но если вы веб разработчик, то знание механизма спекулятивного парсинга поможет использовать его по максимуму.
Различные браузеры предзагружают различные типы ресурсов. Все основные браузеры обязательно предзагружают следующие:
Количество файлов, которые могут быть загружены параллельно, ограничено и варьируется от браузера к браузеру. Это ещё зависит от множество факторов, таких как скачиваются ли файлы из одного сервера или из разных, используется протокол HTTP/1.1 или HTTP/2. Чтобы отрендерить страницу как можно быстрее, браузеры используют сложные алгоритмы и скачивают ресурсы с разными приоритетами, зависящие от типа ресурса, местоположения на странице и состояния самого процесса рендеринга.
При спекулятивном парсинге, браузер не запускает inline JavaScript блоки. Это означает, что если подгружать файлы в скриптах, то они наверняка окажутся последними в очереди на загрузку.
Поэтому это очень важно упростить задачу браузера при загрузке важных ресурсов. Можно, например, вставить их в HTML теги или перенести скрипт загрузки в inline и как можно выше в коде страницы. Хотя иногда требуется наоборот, загрузить файлы как можно позже, т.к. они не столь важные. В таком случае, чтобы спрятать ресурс от спекулятивного парсера, его можно подключить как можно позже на странице через JavaScript. Чтобы узнать больше о том как оптимизировать страницу для спекулятивного парсера, можно пройти по ссылке MDN руководства [на русском].
defer и async
Но скрипты выполняющиеся последовательно остаются проблемой. И не все скрипты действительно важны для пользователя, такие как скприпты аналитики, например. Идеи? Можно загружать их асинхронно.
Атрибуты defer и async были придуманы специально для этого, чтобы дать возможность разработчикам указать какие скрипты можно загружать асинхронно.
Оба этих атрибута подскажут браузеру, что он может продолжить парсить HTML и в тоже время загрузить эти скрипты в фоне. При таком раскладе, скрипты не будут блокировать постройку DOM и рендеринг, в результате чего, пользователь увидит страницу ещё до того, как все скрипты загрузятся.
Разница между defer и async в том, что они начинают выполнять скрипты в разный момент времени.
Где бы они не были указаны, скрипты с async загружаются с низким приоритетом, зачастую после всех остальных скриптов, без блокирования постройки DOM. Но если скрипт с async загрузится быстрее, то его выполнение может заблокировать постройку DOM и все остальные скрипты, которым только предстоит загрузиться.
Замечание: атрибуты async и defer работают только для внешних скриптов. Без параметра src они будут проигнорированы.
preload
async и defer прекрасно подходят если вы не паритесь о некоторых скриптах, но что делать с ресурсами на странице, которые важны для пользователя? Спекулятивные парсеры полезны, но подходят только для горстки типов ресурсов и действуют по своей заложенной логике. В целом, нужно загружать CSS в первую очередь, потому что он блокирует рендеринг, последовательные скрипты должны всегда иметь приоритет выше чем асинхронные, видимые изображения должны быть доступны скорее и есть ещё шрифты, видео, SVG… короче говоря, всё сложно.
Как автор, вам лучше знать какие именно ресурсы важны для рендеринга страницы. Некоторые из них часто погребенны в CSS или скриптах и браузеру придётся пройти сквозь дебри пока он хотя бы до них доберётся. Для таких важных ресурсов теперь можно использовать <link rel=»preload»> чтобы подсказать браузеру загрузить файл как можно скорее.
Все что требуется написать это:
Список того, что можно загрузить весьма велик и атрибут as скажет браузеру, какой именно контент он загружает. Возможные значения этого отрибута:
Шрифты, пожалуй, самый важный элемент для загрузки, который спрятан в CSS. Шрифты необходимы для рендеринга текста на странице, но они не будут загружены пока браузер не удостоверится что именно они будут использованы. А эта проверка происходит только после парсинга и применения CSS и когда стили уже применены к вершинам DOM. Это происходит достаточно поздно в процессе загрузки страницы и как правило приводит к неоправданной задержке рендеринга текста. Этого можно избежать, использовав атрибут preload при загрузке шрифтов. Одна деталь на которую стоит обратить внимание при предзагрузке шрифтов, это то, что нужно устанавливать атрибут crossorigin, даже если шрифт находится на том же домене.
В данное время возможности предзагрузки ограничены и браузеры только начинают применять этот метод, но за прогрессом можно следить тут.
Заключение
Браузеры это сложные существа, которые эволюционируют с 90-х. Мы разобрали некоторые причуды из прошлого равно как и новейшие стандарты в веб разработке. Эти рекомендации помогут сделать сайты приятнее для пользователя.
Если вы хотите узнать больше о работе браузеров, то ниже пара статей, которые могут быть вам интересны:
Async и Defer — стратегии загрузки JavaScript
JavaScript является неотъемлемой частью любого современного веб-приложения, и стратегии, которые мы решаем использовать для загрузки, напрямую влияют на производительность работы этого самого приложения. В данной статье мы попробуем понять важные различия между каждым подходом, плюсы и минусы наряду с последствиями производительности и способы оптимизации по взаимодействию со страницей и времени загрузки.
Для демонстрации я создам веб-сайт, состоящий из следующих внешних зависимостей. Обратите особое внимание на соответствующие размеры файлов, так как время загрузки файлов прямо пропорционально этому.
Подход-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]
HTML5 представил async атрибут script, который помогает в загрузке соответствующих файлов скрипта параллельно на другой поток, не влияя на синтаксический анализ HTML.
Тем не менее, соответствующий сценарий будет проанализирован и выполнен, как только он завершит загрузку, независимо от того, завершен ли или нет синтаксический анализ HTML, и будет иметь ссылку на элемент DOM до этой конкретной точки.
Здесь вы можете четко увидеть, что file2.js был загружен до HTML файла. Однако, в то время как браузер загружает file2, он не приостановил синтаксический анализ HTML и из — за этого, ко времени его выполнения-он имел ссылку на заполнитель html, чтобы ввести динамическое содержимое.
Плюсы: Поскольку скрипты загружаются в другом потоке, синтаксический анализ HTML не будет приостановлен, и пользователь сможет видеть непосредственный контент вместо белого пустого экрана. Основной прирост производительности, т. е. DOMContentLoaded время уменьшилось с 47.68 секунд до всего 21.12 секунд и составляет
Что произойдет, если 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 внутри асинхронных скриптов для выполнения функциональности позже. Пожалуйста, оставьте свои мнения и выводы в комментариях, и я буду рад обсудить их с вами.
статьи IT, javascript, создание сайтов