Атрибуты тега script async и defer. Асинхронная загрузка JavaScript - ускоряем загрузку страниц

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

var script = document .createElement("script" ); script.src = "http://somehost.com/awesome-widget.js" ; document .getElementsByTagName("head" ).appendChild(script);

В чем разница? В «плохом» варианте мы блокируем построение DOM дерева, ждем пока скрипт загрузится, выполнится, а только потом продолжаем обрабатывать остальную часть документа. Во втором примере мы сразу начинаем выполнять сценарий, который создает элемент script , указывающий на внешний ресурс, добавляем его в документ, и продолжаем обработку DOM. Различие тонкое, но очень важное: динамически-создаваемые скрипты не блокирующие.

Так, это же здорово, верно? Динамически-создаваемые скрипты - это вещь! Не так быстро.

Встраиваемый JavaScript имеет небольшой, но важный (и часто упускаемый из виду) подводный камень: CSSOM блокирует его перед выполнением. Почему? Браузер не знает, что именно такой скрипт планирует сделать, а поскольку JavaScript может манипулировать CSS свойствами, он блокируется и ждет, пока анализируется CSS и строится CSSOM. Лучше один раз увидеть, чем сто раз услышать, рассмотрим следующий пример:

Погодите секундочку, что происходит? Оба сценария будут загружены заранее и выполнены через ~2.7 секунды после загрузки страницы. Обратите внимание, что скрипты будут по прежнему выполняться только после того, как будет доступен CSS (~2.7 секунды), но поскольку скрипты уже загружены в тот момент когда становится доступен CSSOM, мы можем выполнять их сразу, экономя более секунды времени обработки. Мы всё делали неправильно?

Прежде чем мы ответим на этот вопрос, давайте рассмотрим еще один пример, на этот раз с атрибутом «async» :

При наличии атрибута async скрипт будет выполнен асинхронно, как только он будет доступен. Если атрибут отсутствует, … то скрипт загружается и выполняется немедленно, прежде чем будет продолжен дальнейший анализ документа.

Атрибут async в теге script реализует два важных свойства: он говорит браузеру, чтобы тот не блокировал построение DOM и не блокировал выполнение скриптов перед построением CSSOM. В результате, скрипты выполняются сразу после того, как загрузятся (~1.6 секунды), не дожидаясь CSSOM. Краткий список результатов:

Так почему мы до сих пор предлагали использовать шаблон, использующий динамически создаваемые скрипты?

Оригинальная статья: Script-injected "async scripts" considered harmful Статью вычитывали: visitorFM , zenwalker , FMRobot

Выход есть: поместить явавские строки в конец html документа (следовательно их загрузка будет происходить после прорисовки всей страницы) и только после этого содержимое блоков будет отображено в нужных местах. Это называется . Все серьезные проекты сегодня стараются как можно быстрее перейти на новую технологию загрузки. Тем более, что это абсолютно легко.

Есть несколько подходов. Начну по порядку.

script src= type= "text/javascript" >

Асинхронная загрузка скрипта HTML5

Стандарт HTML5 поддерживает возможность асинхронной загрузки скриптов, что может значительно ускорить общее время получения страницы. Просто добавьте async или defer .

< script async src= "http://www.site.ru/script.js" type= "text/javascript" >

< script defer src= "http://www.site.ru/script.js" type= "text/javascript" >

Чем же отличаются атрибуты async и defer

В обоих случаях мы получаем асинхронную загрузку скриптов. Разница заключается только в моменте, когда скрипт начинает выполнятся. Скрипт с атрибутом async выполнится при первой же возможности после его полной загрузки, но до загрузки объекта window. В случае использования атрибута defer – скрипт не нарушит порядок своего выполнения по отношению к остальным скриптам и его выполнение произойдет после полной загрузки и парсинга страницы, но до события DOMContentLoaded объекта document.

К сожалению, этот механизм на сегодняшний день не работает во всех браузерах (особенно это касается IE). Также не будет работать, если в файле script.js есть строки document.write .

Асинхронная загрузка javascript скриптом от Google

Как известно всем мастерам, Google уделяет особое внимание скорости загрузки сайтов, и понижает медленные в поисковой выдаче. Что бы помочь, Гугл разработал специальный скрипт, при помощи которого можно сделать асинхронную загрузку javascript.

Чтобы использовать, просто заменяем

на

И подключаем файл скрипта extsrc.js

Получится так:

< script src= "http://extsrcjs.googlecode.com/svn/trunk/extsrc.js" > < script extsrc= "...." >

К сожалению, этот способ тоже не подойдет к файлам с document.write

Лучшая рабочая асинхронная загрузка javascript

Универсальный способ для всех браузеров. Работает даже с document.write

В том месте страницы, где нужно реально отобразить наш элемент создаем пустой div блок:

< div id= "script_block" class = "script_block" >

В самом конце страницы перед вставляем скрипт для асинхронной загрузки файлов:

< div id= "script_ad" class = "script_ad" style= "display:none;" > Здесь любой файл или скрипт, который нужно загрузить. < script type= "text/javascript" > // переместить его в реальную позицию отображения document. getElementById("script_block" ) . appendChild(document. getElementById("script_ad" ) ) ; // показать document. getElementById("script_ad" ) . style. display = "block" ;

В самых старых версиях IE (6 и ниже) асинхронная загрузка к сожалению не работает, но таких пользователей уже практически нет. Все остальные браузеры и сервисы успешно пользуются современной ускоренной загрузкой web-страниц.

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]

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 - то есть могу доработать ваш сайт или помочь с веб-программированием.

The HTML element is used to embed or reference executable code; this is typically used to embed or refer to JavaScript code. The element can also be used with other languages, such as WebGL "s GLSL shader programming language.

Content categories Permitted content Tag omission Permitted parents Permitted ARIA roles DOM interface
Metadata content , Flow content , Phrasing content .
Dynamic script such as text/javascript .
None, both the starting and ending tag are mandatory.
Any element that accepts metadata content , or any element that accepts phrasing content .
None
HTMLScriptElement
Attributes async HTML5

For classic scripts, if the async attribute is present, then the classic script will be fetched in parallel to parsing and evaluated as soon as it is available.

crossorigin Normal script elements pass minimal information to the window.onerror for scripts which do not pass the standard CORS checks. To allow error logging for sites which use a separate domain for static media, use this attribute. See CORS settings attributes for a more descriptive explanation of its valid arguments. defer

This Boolean attribute is set to indicate to a browser that the script is meant to be executed after the document has been parsed, but before firing DOMContentLoaded .

Scripts with the defer attribute will prevent the DOMContentLoaded event from firing until the script has loaded and finished evaluating.

This attribute must not be used if the src attribute is absent (i.e. for inline scripts), in this case it would have no effect.

Examples Basic usage

These examples show how to import (an external) script using the element.

And the following examples show how to put (an inline) script inside the element.

alert("Hello World!");

Module fallback

Browsers that support the module value for the type attribute ignore any script with a nomodule attribute. That enables you to use module scripts while also providing nomodule -marked fallback scripts for non-supporting browsers.

Specifications Specification Status Comments
HTML Living Standard
The definition of "" in that specification.
Living Standard Removed the charset attribute
HTML5
The definition of "" in that specification.
Recommendation
HTML 4.01 Specification
The definition of "" in that specification.
Recommendation
Browser compatibility

The compatibility table in this page is generated from structured data. If you"d like to contribute to the data, please check out https://github.com/mdn/browser-compat-data and send us a pull request.

Update compatibility data on GitHub

Desktop Mobile Chrome Edge Firefox Internet Explorer Opera Safari Android webview Chrome for Android Firefox for Android Opera for Android Safari on iOS Samsung Internet script async crossorigin defer integrity language

Deprecated Non-standard

nomodule

Experimental

referrerPolicy src text type type.module type: The version parameter of the type attribute

Non-standard

Chrome Full support 1 Edge Full support 12 Firefox Full support 1

Notes

Full support 1

Notes

Notes Starting in Firefox 4, inserting elements that have been created by calling document.createElement("script") no longer enforces execution in insertion order. This change lets Firefox properly abide by the specification. To make script-inserted external scripts execute in their insertion order, set .async=false on them.
IE Full support Yes Opera Full support Yes Safari Full support Yes
Chrome Full support 1 Edge Full support 12 Firefox Full support 1 IE Full support Yes Opera Full support Yes Safari Full support Yes WebView Android Full support Yes Chrome Android Full support Yes Firefox Android Full support 4 Opera Android Full support Yes Safari iOS Full support Yes Samsung Internet Android Full support Yes
Chrome Full support 30 Edge Full support ≤18 Firefox Full support 13 IE No support No Opera Full support 12 Safari Full support Yes

Notes

Full support Yes

Notes

Notes The crossorigin attribute was implemented in WebKit in WebKit bug 81438 .
WebView Android Full support Yes Chrome Android Full support Yes Firefox Android Full support 14 Opera Android ? Safari iOS ? Samsung Internet Android Full support Yes
Chrome Full support Yes

Notes

Full support Yes

Notes

Notes Chromium Issue #611136 , Chromium Issue #874749
Edge Full support 12 Firefox Full support 3.5

Notes

Full support 3.5

Notes

Notes Since Firefox 3.6, the defer attribute is ignored on scripts that don"t have the src attribute. However, in Firefox 3.5 even inline scripts are deferred if the defer attribute is set.
IE Full support 10

Notes

Full support 10

Notes

Notes In versions prior to Internet Explorer 10, it implemented defer by a proprietary specification. Since version 10 it conforms to the W3C specification.
Opera Full support Yes

Notes

Full support Yes

Notes

Notes Chromium Issue #611136 , Chromium Issue #874749
Safari Full support Yes WebView Android Full support Yes

Notes

Full support Yes

Notes

Notes WebView does not defer scripts with the defer attribute when the page is served as XHTML (application/xhtml+xml) - Chromium Issue #611136 , Chromium Issue #874749
Chrome Android Full support Yes

Notes

Full support Yes

Notes

Notes Chrome does not defer scripts with the defer attribute when the page is served as XHTML (application/xhtml+xml) - Chromium Issue #611136 , Chromium Issue #874749
Firefox Android Full support 4 Opera Android Full support Yes

Notes

Full support Yes

Notes

Notes Opera does not defer scripts with the defer attribute when the page is served as XHTML (application/xhtml+xml) - Chromium Issue #611136 , Chromium Issue #874749
Safari iOS Full support Yes Samsung Internet Android Full support Yes

Notes

Full support Yes

Notes

Notes Samsung Internet does not defer scripts with the defer attribute when the page is served as XHTML (application/xhtml+xml) - Chromium Issue #611136 , Chromium Issue #874749
Chrome Full support 45 Edge Partial support 17 Firefox Full support 43 IE No support No Opera Full support Yes Safari Full support Yes WebView Android Full support 45 Chrome Android Full support 45 Firefox Android Full support 43 Opera Android ? Safari iOS No support No Samsung Internet Android Full support 5.0
Chrome Full support 1 Edge Full support 12 Firefox Full support 1 IE Full support Yes Opera Full support Yes Safari Full support Yes WebView Android Full support Yes Chrome Android Full support Yes Firefox Android Full support 4 Opera Android Full support Yes Safari iOS Full support Yes Samsung Internet Android Full support Yes
Chrome Full support 61 Edge Full support 16 Firefox Full support 60 Full support 60 No support 55 - 60

Disabled

Disabled
IE No support No Opera Full support 48 Safari Full support 11 Firefox Android Full support 60 Full support 60 No support 55 - 60

Disabled

Disabled From version 55 until version 60 (exclusive): this feature is behind the dom.moduleScripts.enabled preference (needs to be set to true). To change preferences in Firefox, visit about:config.
Safari iOS Full support 11
Chrome Full support 70 Edge Full support ≤79 Firefox Full support 65 IE No support No Opera Full support Yes Safari No support No WebView Android Full support 70 Chrome Android Full support 70 Firefox Android Full support 65 Opera Android ? Safari iOS No support No Samsung Internet Android Full support 10.0
Chrome Full support 1 Edge Full support 12 Firefox Full support 1 IE Full support Yes Opera Full support Yes Safari Full support Yes WebView Android Full support Yes Chrome Android Full support Yes Firefox Android Full support 4 Opera Android Full support Yes Safari iOS Full support Yes Samsung Internet Android Full support Yes
Chrome Full support 1 Edge Full support 12 Firefox Full support 1 IE Full support Yes Opera Full support Yes Safari Full support Yes WebView Android Full support Yes Chrome Android Full support Yes Firefox Android Full support 4 Opera Android Full support Yes Safari iOS Full support Yes Samsung Internet Android Full support Yes
Chrome Full support 1 Edge Full support 12 Firefox Full support 1 IE Full support Yes Opera Full support Yes Safari Full support Yes WebView Android Full support Yes Chrome Android Full support Yes Firefox Android Full support 4 Opera Android Full support Yes Safari iOS Full support Yes Samsung Internet Android Full support Yes
Chrome Full support 61 Edge Full support 16 Firefox Full support 60 Full support 60 No support 54 - 60

Disabled

Disabled
IE No support No Opera Full support 48 Safari Full support 10.1 WebView Android Full support 61 Chrome Android Full support 61 Firefox Android Full support 60 Full support 60 No support 54 - 60

Disabled

Disabled From version 54 until version 60 (exclusive): this feature is behind the dom.moduleScripts.enabled preference (needs to be set to true). To change preferences in Firefox, visit about:config.
Opera Android Full support 45 Safari iOS Full support 10.3 Samsung Internet Android Full support 8.0
Chrome No support No Edge No support No Firefox No support ? - 59 IE No support No Opera No support No Safari No support No WebView Android No support No Chrome Android No support No Firefox Android No support ? - 59 Opera Android No support No Safari iOS No support No Samsung Internet Android No support No
Legend Full support Full support Partial support Partial support No support No support Compatibility unknown Compatibility unknown Experimental. Expect behavior to change in the future. Experimental. Expect behavior to change in the future. Non-standard. Expect poor cross-browser support. Non-standard. Expect poor cross-browser support. Deprecated. Not for use in new websites. Deprecated. Not for use in new websites. See implementation notes. See implementation notes. User must explicitly enable this feature. Compatibility notes

In older browsers that don"t support the async attribute, parser-inserted scripts block the parser; script-inserted scripts execute asynchronously in IE and WebKit, but synchronously in Opera and pre-4 Firefox. In Firefox 4, the async DOM property defaults to true for script-created scripts, so the default behaviour matches the behaviour of IE and WebKit.

To request script-inserted external scripts be executed in the insertion order in browsers where the document.createElement("script").async evaluates to true (such as Firefox 4), set async="false" on the scripts you want to maintain order.

Never call document.write() from an async script. In Firefox 3.6, calling document.write() has an unpredictable effect. In Firefox 4, calling document.write() from an async script has no effect (other than printing a warning to the error console).

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

Такое поведение может тормозить отображение HTML, когда на странице загружаются много файлов JavaScript. Часто код этих файлов не нужен, чтобы показать HTML страницы. Именно поэтому рекомендуется подключать скрипты в конце страницы. Однако эту рекомендацию не всегда можно соблюсти и для таких случаев есть другие способы не блокировать отрисовку HTML.

У элемента есть два атрибута, async и defer , которые могут дать нам больше контроля над тем, как и когда файл загружаются и выполняются.

Обычное выполнение ... ... .... Атрибут async
Атрибут defer
Где и что использовать?

Зависит от ситуации. Рассмотрим несколько вопросов на эту тему.

Где находится элемент ?

Если файл JavaScript находится непосредственно перед закрывающим тегом , использовать async или defer не имеет смысла поскольку к этому моменту парсер уже проанализировал весь HTML код.

Является ли скрипт самодостаточным?

Для файлов (скриптов), которые не нужны для работы других скриптов и сами не имеют зависимостей от других скриптов, атрибут async особенно полезен. Поскольку в этом случае все равно, в какой именно момент выполняется скрипт, асинхронная загрузка является наиболее подходящим вариантом.

Нужен ли полностью загруженный DOM для работы скрипта?

Если это необходимо, то использование async уместно только в случае если скрипт рассчитан на асинхронную загрузку - т.е. он ждет события пока загрузиться DOM и только потом начинает свою работу.

Или можно использовать атрибут defer . В этом случае размещать вызов script можно в любом месте HTML.

Маленький ли скрипт?

Если скрипт относительно мал и от него зависят или он зависит от других скриптов, то его можно встроить прямо в HTML (подключить inline).

Поддержка браузерами 97% Добавление атрибутов defer или async в WordPress

Штатных способов сделать это нет, поэтому будем пользоваться хуком script_loader_tag :

Add_action("wp_enqueue_scripts", "my_scripts_method"); function my_scripts_method(){ // подключаем скрипт wp_enqueue_script("my-script", get_template_directory_uri() . "/js/my-script.js"); // Добавляем атрибут defer к скрипту с id `my-script` add_filter("script_loader_tag", "change_my_script", 10, 3); function change_my_script($tag, $handle, $src){ if("my-script" === $handle){ // return str_replace(" src", " async src", $tag); return str_replace(" src", " defer src", $tag); } return $tag; } }

Статьи по теме: