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 - то есть могу доработать ваш сайт или помочь с веб-программированием.
Выход есть: поместить явавские строки в конец 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-страниц.
Синхронные скрипты - это плохо, они заставляют браузер блокировать построение 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Подключаемые скрипты (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; } }
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.
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 |
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. deferThis 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 usageThese 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 fallbackBrowsers 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
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 |
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 | |||||||||||
Chrome Full support 1 | Edge Full support 12 | Firefox
Full support
1 Notes Full support 1Notes 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 YesNotes 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 YesNotes Notes Chromium Issue #611136 , Chromium Issue #874749 | Edge Full support 12 | Firefox
Full support
3.5 Notes Full support 3.5Notes 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 10Notes 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 YesNotes Notes Chromium Issue #611136 , Chromium Issue #874749 | Safari Full support Yes | WebView Android
Full support
Yes Notes Full support YesNotes 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 YesNotes 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 YesNotes 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 YesNotes 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 |
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).