http://wm-monitoring.ru/ ')) {alert('Спасибо за то что установили нашу кнопку! =)');} else {alert('Очень жаль! =(');}"> http://wm-monitoring.ru/

Глубокое погружение в мутные воды сценария loading

  1. Вступление
  2. Мой первый скрипт включает
  3. Спасибо IE! (нет, я не саркастичен)
  4. Спасибо IE! (хорошо, теперь я с сарказмом)
  5. 1.js
  6. 2.js
  7. HTML5 на помощь
  8. Я знаю, что нам нужно, библиотека JavaScript!
  9. ДОМ на помощь
  10. Это самый быстрый способ загрузки скриптов, верно? Правильно?
  11. Я нахожу эту статью удручающей.
  12. У IE есть идея!
  13. Довольно! Как мне загрузить скрипты?
  14. Eww, должно быть что-то лучшее, что мы можем использовать сейчас?
  15. Краткий справочник
  16. Перенести
  17. асинхронный
  18. Асинхронный ложный

Вступление

В этой статье я собираюсь научить вас, как загрузить и запустить JavaScript в браузере.

Нет, подожди, вернись! Я знаю, это звучит обыденно и просто, но помните, что это происходит в браузере, где теоретически простое становится наследственной причудой. Зная эти причуды, вы сможете выбрать самый быстрый и наименее разрушительный способ загрузки скриптов. Если у вас плотный график, перейдите к краткий справочник ,

Для начала вот как спецификация определяет различные способы загрузки и выполнения скрипта:

Для начала вот как   спецификация   определяет различные способы загрузки и выполнения скрипта:

WHATWG при загрузке скрипта

Как и все спецификации WHATWG, изначально это выглядело как последствия кластерной бомбы на фабрике скрэббл, но как только вы прочитали ее в пятый раз и вытерли кровь с ваших глаз, на самом деле это довольно интересно:

Мой первый скрипт включает

<script src = "// other-domain.com/1.js"> </ script> <script src = "2.js"> </ script>

Ах, блаженная простота. Здесь браузер будет загружать оба сценария параллельно и выполнять их как можно скорее, поддерживая их порядок. «2.js» не будет выполняться до тех пор, пока «1.js» не выполнится (или не выполнится), «1.js» не будет выполняться, пока не будет выполнен предыдущий скрипт или таблица стилей и т. Д. И т. Д.

К сожалению, браузер блокирует дальнейшую визуализацию страницы, пока все это происходит. Это происходит из-за API-интерфейсов DOM из «первого века Интернета», которые позволяют добавлять строки к содержимому, которое просматривает синтаксический анализатор, например, document.write. Более новые браузеры будут продолжать сканировать или анализировать документ в фоновом режиме и запускать загрузку для внешнего содержимого, в котором оно может нуждаться (js, images, css и т. Д.), Но рендеринг по-прежнему блокируется.

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

Спасибо IE! (нет, я не саркастичен)

<script src = "// other-domain.com/1.js" defer> </ script> <script src = "2.js" defer> </ script>

Microsoft распознала эти проблемы с производительностью и ввела «отсрочку» в Internet Explorer 4. В основном это говорит: «Я обещаю не вводить что-либо в анализатор, используя такие вещи, как document.write. Если я нарушу это обещание, вы можете наказать меня так, как считаете нужным ». Этот атрибут сделал это в HTML4 и появился в других браузерах.

В приведенном выше примере браузер будет загружать оба сценария параллельно и выполнять их непосредственно перед запуском DOMContentLoaded, поддерживая их порядок.

Как кассетная бомба на овечьей фабрике, «отсрочка» превратилась в шерстяной беспорядок. Между атрибутами «src» и «defer» и тегами сценария и динамически добавляемыми сценариями у нас есть 6 шаблонов добавления сценария. Конечно, браузеры не согласились с порядком, который они должны выполнить. Mozilla написала отличную статью о проблеме как это было еще в 2009 году.

WHATWG сделал поведение явным, заявив, что «отсрочка» не влияет на сценарии, которые были динамически добавлены или не содержали «src». В противном случае отложенные сценарии должны запускаться после анализа документа в порядке их добавления.

Спасибо IE! (хорошо, теперь я с сарказмом)

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

1.js

console.log ( '1'); document.getElementsByTagName ('p') [0] .innerHTML = 'Изменение некоторого содержимого'; console.log ( '2');

2.js

console.log ( '3');

Предполагая, что на странице есть абзац, ожидаемый порядок журналов - [1, 2, 3], хотя в IE9 и ниже вы получите [1, 3, 2]. Отдельные операции DOM заставляют IE приостанавливать текущее выполнение сценария и выполнять другие ожидающие сценарии перед продолжением.

Однако даже в реализациях без ошибок, таких как IE10 и другие браузеры, выполнение скрипта откладывается до тех пор, пока весь документ не будет загружен и проанализирован. Это может быть удобно, если вы все равно будете ждать DOMContentLoaded, но если вы хотите быть очень агрессивным с производительностью, вы можете начать добавлять слушателей и загружать их быстрее…

HTML5 на помощь

<script src = "// other-domain.com/1.js" async> </ script> <script src = "2.js" async> </ script>

HTML5 дал нам новый атрибут «async», который предполагает, что вы не собираетесь использовать document.write, но не ожидает, пока документ не будет проанализирован для выполнения. Браузер загрузит оба сценария параллельно и выполнит их как можно скорее.

К сожалению, поскольку они будут выполнены как можно скорее, «2.js» может выполняться раньше, чем «1.js». Это нормально, если они независимы, возможно, «1.js» - это скрипт отслеживания, который не имеет ничего общего с «2.js». Но если ваш «1.js» является CDN-копией jQuery, от которой зависит «2.js», ваша страница будет покрыта ошибками, как кластерная бомба в… Я не знаю… У меня ничего нет для этот.

Я знаю, что нам нужно, библиотека JavaScript!

У Святого Грааля есть набор скриптов, загружаемых немедленно, без блокировки рендеринга, и выполняются как можно скорее в порядке их добавления. К сожалению, HTML ненавидит вас и не позволит вам сделать это.

Проблема была решена с помощью JavaScript в нескольких вариантах. Некоторые требовали, чтобы вы внесли изменения в свой JavaScript, заключив его в обратный вызов, который библиотека вызывает в правильном порядке (например, RequireJS ). Другие будут использовать XHR для параллельной загрузки, а затем eval () в правильном порядке, что не работает для сценариев в другом домене, если у них нет Заголовок CORS и браузер это поддержал. Некоторые даже использовали сверхмагические хаки, такие как LabJS ,

Взломы включали в себя хитрость браузера, заставляющую загружать ресурс таким образом, чтобы запускать событие по завершении, но избегать его выполнения. В LabJS сценарий будет добавлен с неверным типом MIME, например <script type = "script / cache" src = "...">. Как только все скрипты будут загружены, они будут добавлены снова с правильным типом, надеясь, что браузер получит их прямо из кэша и выполнит их немедленно, по порядку. Это зависело от удобного, но неопределенного поведения и нарушалось, когда объявленные HTML5 браузеры не должны загружать скрипты с нераспознанным типом. Стоит отметить, что LabJS адаптировался к этим изменениям и теперь использует комбинацию методов из этой статьи.

Однако у загрузчиков сценариев есть собственная проблема с производительностью, вам нужно подождать, пока библиотека JavaScript загрузит и проанализирует, прежде чем любой из сценариев, которыми она управляет, сможет начать загрузку. Кроме того, как мы собираемся загрузить загрузчик скриптов? Как мы собираемся загрузить скрипт, который сообщает загрузчику скрипта, что загружать? Кто смотрит на сторожей? Почему я голый? Это все сложные вопросы.

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

ДОМ на помощь

Ответ на самом деле в спецификации HTML5, хотя он скрыт внизу раздела загрузки скриптов.

Атрибут async IDL определяет, будет ли элемент выполняться асинхронно или нет. Если флаг элемента «force-async» установлен, то при получении атрибут async IDL должен возвращать true, а при установке флаг «force-async» сначала должен быть сброшен…

Давайте переведем это на «землянин»:

['//other-domain.com/1.js', '2.js'] .forEach (function (src) {var script = document.createElement ('script'); script.src = src; document.head .appendChild (script);});

Скрипты, которые динамически создаются и добавляются в документ, по умолчанию являются асинхронными , они не блокируют рендеринг и не выполняются сразу после загрузки, что означает, что они могут появиться в неправильном порядке. Однако мы можем явно пометить их как не асинхронные:

['//other-domain.com/1.js', '2.js'] .forEach (function (src) {var script = document.createElement ('script'); script.src = src; script.async = false; document.head.appendChild (script);});

Это дает нашим сценариям сочетание поведения, которого невозможно достичь с помощью простого HTML. Будучи явно не асинхронными, сценарии добавляются в очередь выполнения, ту же очередь, в которую они добавлены в нашем первом примере с простым HTML. Однако, создаваясь динамически, они выполняются вне синтаксического анализа документа, поэтому рендеринг не блокируется во время загрузки (не путайте загрузку не-асинхронных скриптов с синхронизацией XHR, что никогда не бывает полезным).

Приведенный выше сценарий должен быть встроен в заголовок страниц, сценарий очередей загружается как можно быстрее, не прерывая прогрессивный рендеринг, и выполняется как можно скорее в указанном вами порядке. «2.js» можно загрузить до версии «1.js», но она не будет выполняться до тех пор, пока «1.js» не загрузится и не выполнится успешно или не выполнит одно из этих действий. Ура! асинхронная загрузка, но заказанное выполнение!

Загрузка скриптов таким способом поддерживается все, что поддерживает атрибут async , за исключением Safari 5.0 (5.1 хорошо). Кроме того, все версии Firefox и Opera поддерживаются как версии, которые не поддерживают атрибут async, для удобного выполнения динамически добавляемых сценариев в порядке их добавления в документ в любом случае.

Это самый быстрый способ загрузки скриптов, верно? Правильно?

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

Мы можем добавить обнаруживаемость обратно, поместив это в заголовок документа:

<link rel = "subresource" href = "// other-domain.com/1.js"> <link rel = "subresource" href = "2.js">

Это говорит браузеру, что странице нужны 1.js и 2.js. ссылка [rel = subresource] аналогична ссылке [rel = prefetch], но с разная семантика , К сожалению, в настоящее время он поддерживается только в Chrome, и вам нужно объявить, какие скрипты загружать дважды, один раз с помощью элементов ссылок и снова в вашем скрипте.

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

Я нахожу эту статью удручающей.

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

С HTTP2 / SPDY вы можете сократить накладные расходы на запрос до такой степени, что доставка сценариев в нескольких небольших индивидуально кэшируемых файлах может быть самым быстрым способом. Представить:

<script src = "dependencies.js"> </ script> <script src = "усиление-1.js"> </ script> <script src = "усиление-2.js"> </ script> <script src = "extension-3.js"> </ script>… <script src = «extension-10.js»> </ script>

Каждый скрипт улучшения имеет дело с определенным компонентом страницы, но требует наличия служебных функций в dependencies.js. В идеале мы хотим загрузить все асинхронно, а затем выполнить скрипты расширения как можно скорее, в любом порядке, но после файла dependencies.js. Это прогрессивное прогрессивное улучшение!

К сожалению, не существует декларативного способа добиться этого, если сами скрипты не модифицированы для отслеживания состояния загрузки зависимостей.js. Даже async = false не решает эту проблему, так как выполнение Enhance-10.js заблокируется на 1-9. На самом деле, есть только один браузер, который делает это возможным без хаков ...

У IE есть идея!

IE загружает скрипты иначе, чем в других браузерах.

var script = document.createElement ('script'); script.src = 'what.js';

IE начинает загрузку «what.js» сейчас, другие браузеры не начинают загрузку, пока скрипт не будет добавлен в документ. В IE также есть событие «readystatechange» и свойство «readystate», которые сообщают нам о ходе загрузки. Это действительно очень полезно, так как позволяет нам независимо контролировать загрузку и выполнение скриптов.

var script = document.createElement ('script'); script.onreadystatechange = function () {if (script.readyState == 'загружен') {// Наш скрипт загружен, но не выполнен. // Он не будет выполняться, пока мы не выполним: document.body.appendChild (script); }}; script.src = 'what.js';

Мы можем создавать сложные модели зависимостей, выбирая, когда добавлять сценарии в документ. IE поддерживает эту модель начиная с версии 6. Довольно интересно, но она все еще страдает от той же проблемы обнаружения предзагрузчика, что и async = false.

Довольно! Как мне загрузить скрипты?

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

<script src = "// other-domain.com/1.js"> </ script> <script src = "2.js"> </ script>

Тот. В конце элемента тела. Да, быть веб-разработчиком очень похоже на то, чтобы быть королем Сизифом (бум! 100 хипстерских очков за греческую мифологию!). Ограничения в HTML и браузерах мешают нам работать намного лучше.

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

Eww, должно быть что-то лучшее, что мы можем использовать сейчас?

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

Сначала добавим декларацию подресурса для предзагрузчиков:

<link rel = "subresource" href = "// other-domain.com/1.js"> <link rel = "subresource" href = "2.js">

Затем, встроенный в заголовок документа, мы загружаем наши сценарии с помощью JavaScript, используя async = false, возвращаясь к загрузке сценариев в IE на основе readystate и возвращаясь к отложению.

var scripts = ['1.js', '2.js']; var src; скрипт var; var pendingScripts = []; var firstScript = document.scripts [0]; // Наблюдаем за загрузкой скриптов в IE function stateChange () {// Выполняем столько скриптов по порядку, сколько мы можем варьировать pendingScript; while (pendingScripts [0] && pendingScripts [0] .readyState == 'загружен') {pendingScript = pendingScripts.shift (); // избежать будущих событий загрузки из этого скрипта (например, если src изменится) pendingScript.onreadystatechange = null; // не могу просто добавить appendChild, старая ошибка IE, если элемент не закрыт firstScript.parentNode.insertBefore (pendingScript, firstScript); }} // перебираем ссылки на наши скрипты while (src = scripts.shift ()) {if ('async' в firstScript) {// современные браузеры script = document.createElement ('script'); script.async = false; script.src = src; document.head.appendChild (сценарий); } else if (firstScript.readyState) {// IE <10 // создайте скрипт и добавьте его в нашу кучу script = document.createElement ('script'); pendingScripts.push (сценарий); // прослушивать изменения состояния script.onreadystatechange = stateChange; // необходимо установить src ПОСЛЕ добавления слушателя onreadystatechange // иначе мы пропустим загруженное событие для кэшированных скриптов script.src = src; } else {// возвращаемся к отсрочке document.write ('<script src = "' + src + '" defer> </' + 'script>'); }}

Несколько трюков и минимизация позже, это 362 байта + URL вашего скрипта:

! function (e, t, r) {function n () {for (; d [0] && "загружен" == d [0] [f];) c = d.shift (), c [o] = ! i.parentNode.insertBefore (c, i)} для (var s, a, c, d = [], i = e.scripts [0], o = "onreadystatechange", f = "readyState"; s = r .shift ();) a = e.createElement (t), «async» в i? (a.async =! 1, e.head.appendChild (a)): i [f]? (d.push (a ), a [o] = n): e.write ("<" + t + 'src = "' + s + '" defer> </' + t + ">"), a.src = s} (document, " скрипт ", [" //other-domain.com/1.js "," 2.js "])

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

Фу, теперь я знаю, почему раздел загрузки WHATWG-скрипта такой большой. Мне нужно выпить.

Краткий справочник

Простые элементы сценария

<script src = "// other-domain.com/1.js"> </ script> <script src = "2.js"> </ script>

Спецификация говорит: скачать вместе, выполнить по порядку после любого ожидающего CSS, блокировать рендеринг до завершения.

Браузеры говорят: да, сэр!

Перенести

<script src = "// other-domain.com/1.js" defer> </ script> <script src = "2.js" defer> </ script>

Spec говорит: скачать вместе, выполнить по порядку перед DOMContentLoaded. Игнорируйте «defer» в сценариях без «src».

IE <10 говорит: я мог бы выполнить 2.js в середине выполнения 1.js. Разве это не весело?

браузеры в красном сказать: я понятия не имею, что это за «отсрочка», я собираюсь загрузить сценарии, как если бы их не было.

Другие браузеры говорят: хорошо, но я не могу игнорировать «defer» в сценариях без «src».

асинхронный

<script src = "// other-domain.com/1.js" async> </ script> <script src = "2.js" async> </ script>

Спец говорит: скачать вместе, выполнять в любом порядке, в котором они скачивают.

браузеры в красном сказать: что такое "асинхронный"? Я собираюсь загрузить сценарии, как будто их там не было.

Другие браузеры говорят: Да, хорошо.

Асинхронный ложный

['1.js', '2.js'] .forEach (function (src) {var script = document.createElement ('script'); script.src = src; script.async = false; document.head.appendChild (скрипт);});

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

Firefox <3.6, Opera говорит: я понятия не имею, что это за «асинхронная» вещь, но так получилось, что я выполняю сценарии, добавленные через JS, в порядке их добавления.

Safari 5.0 говорит: « Я понимаю« асинхронный », но не понимаю, как установить« ложный »с JS. Я выполню ваши сценарии, как только они приземлятся, в любом порядке.

IE <10 говорит: нет понятия об «асинхронности», но есть обходной путь используя «onreadystatechange».

Другой браузеры в красном Скажите: я не понимаю эту «асинхронную» вещь, я выполню ваши сценарии, как только они приземлятся, в любом порядке.

Все остальное говорит: я твой друг, мы собираемся сделать это по книге.

Карта