Примечание: ниже расположен перевод заметки Loading Scripts Without Blocking от Steve Souders. Автор рассматривает в ней различные методы загрузки скриптов и выделяет наиболее применимые для каждого конкретного случая. Мои комментарии далее курсивом.
В основе этой заметки лежит четвертая часть из книги Even Faster Web Sites, которая является продолжением High Performance Web Sites. Эту часть также покрывают следующие заметки: chapters and contributing authors, Splitting the Initial Payload и Стыкуем асинхронные скрипты. Я буду продолжать писать заметки с отрывками из книги вплоть до даты ее выхода, ориентировочно, это середина июня.
Чем больше сайтов вокруг внедряют приложения «Веб 2.0», тем больше становится JavaScript, загружаемого на страницах этих сайтов. Существует ряд проблем с производительностью таких страниц, поскольку сами скрипты могут в значительной степени ее ухудшать (например, в IE 6 и 7). Это проявляется в двух возможных блокировках:
В качестве примера можно привести следующую страницу. На ней расположены 2 скрипта, за которыми следует картинка, файл стилей и iframe
. По диаграмме загрузки в IE7 прекрасно видно, что первый скрипт блокирует все нижележащие файлы, а после этого точно так же поступает и второй скрипт, наконец, картинка, стили и iframe
все загружаются параллельно. При загрузке страницы мы сразу видим параграф текста, расположенный выше скрипта. Однако отображение остального текста в HTML-документе блокируется до тех пор, пока не загрузятся все остальные скрипты.
Скрипты блокируют загрузку в IE6&7, Firefox 2&3.0, Safari 3, Chrome 1 и Opera
Браузеры однопоточны, поэтому понятно, почему во время выполнения скрипта браузер не может начать остальные загрузки. Но нет на самом деле никакой причины (Steve немного перегибает палку: причины есть, и они весьма существенные, в частности, в скрипте может быть заложено изменение нижележащего DOM-дерева или редирект на другую страницу), чтобы блокировать загрузку других компонентов, пока идет загрузка скрипта. И это именно то, что современные браузеры, включая Internet Explorer 8, Safari 4 и Chrome 2, уже сделали. Диаграмма загрузки для описанного примера блокировки загрузки в IE8 показывает, что скрипты загружаются в параллельном режиме, равно как и таблицы стилей. Но картинка и iframe
остаются по-прежнему заблокированными. Safari 4 и Chrome 2 поступают точно так же. Параллельные загрузки становятся лучше, но все еще далеки от совершенства.
Скрипты по-прежнему блокируют загрузку, даже в IE8, Safari 4 и Chrome 2
Однако, на наше счастье, существуют и другие методы загрузить скрипты и не заблокировать остальные ресурсы. Плохая новость заключается в том, что каждый из этих методов заставляет веб-разработчиков немного напрячься.
Существует 6 основных техник для неблокируемой загрузки скриптов:
XHR
и выполняем eval()
для responseText
.XHR
и вставляем его на страницу, создавая новый элемент script
и выставляя у этого элемента text
равным responseText
.iframe
.script
и назначаем его свойства src
равным соответствующему URL.script
атрибут defer
. Это работало только в IE, но сейчас доступно и в Firefox 3.1.document.write
для скрипта. Вписываем HTML-код <script src="">
прямо на страницу при помощи document.write
. Это загружает скрипты без блокировки только в IE.Каждый из этих методов можно «пощупать» при помощи Cuzillion. В резульате можно заметить, что методы достаточно существенно отличаются друг от друга, что приведено в следующей таблице. Большинство из них обеспечивает параллельную загрузку скриптов, хотя defer
и document.write
не везде работают. Также часть описанных приемов не может быть использована для межсайтовых (cross-site) скриптов, а для некоторых требуется незначительная модификация текущих скриптов, чтобы заставить их работать в этом режиме. В качестве дополнительных отличий, которые не так часто упоминаются в обзорах, стоит отметить, затрагивают ли методы отображение состояния загрузки в браузере (индикатор состояния, индикатор загрузки, иконку вкладки и курсор мыши). Если вы используете несколько скриптов с частичной зависимостью друг от друга, вам также потребуется выбрать тот метод, который обеспечивает порядок выполнения скриптов.
Метод | Параллельность | Домен может отличаться | Не требует модификации | Индикаторы загрузки | Сохраняет порядок | Размер (в байтах) |
---|---|---|---|---|---|---|
XHR выполнение | IE, FF, Saf, Chr, Op | нет | нет | Saf, Chr | - | ~500 |
XHR вставка | IE, FF, Saf, Chr, Op | нет | да | Saf, Chr | - | ~500 |
Скрипт в iframe | IE, FF, Saf, Chr, Op | нет | нет | IE, FF, Saf, Chr | - | ~50 |
DOM-элемент для скрипта | IE, FF, Saf, Chr, Op | да | да | FF, Saf, Chr | FF, Op | ~200 |
Defer для скрипта | IE, Saf4, Chr2, FF3.1 | да | да | IE, FF, Saf, Chr, Op | IE, FF, Saf, Chr, Op | ~50 |
document.write для скрипта | IE, Saf4, Chr2, Op | да | да | IE, FF, Saf, Chr, Op | IE, FF, Saf, Chr, Op | ~100 |
И все же: какой из этих методов самый лучший? Ответ на этот вопрос будет зависеть от вашей конкретной ситуации. Приведенное ниже дерево решения можно использовать как руководство. На самом деле оно не такое сложное, каким кажется. На принятие решения влияют только три параметра: расположен ли скрипт на том же домене, что и главная страница; необходимо ли сохранить порядок выполнения скриптов; и стоит ли вызывать срабатывание индикаторов загрузки.
Дерево решения для выбора оптимальной техники асинхронной загрузки
В идеале вся логика для этого решения должна быть внедрена в какой-нибудь популярный язык создания шаблонов HTML-разметки (PHP, Python, Perl и др.) так, чтобы разработчики могли просто вызвать загрузку скрипта и быть уверенным, что она пойдет по наиболее оптимальному пути.
В большинстве случаев наилучшим выбором будет создание узла DOM-дерева. Это работает во всех браузерах, не имеет ограничений на использование доменов, и внедрить так же просто, как понять механизм передачи (даже Google AutoComplete его использует). Единственная проблема с этим подходом заключается в том, что он не сохраняет порядок исполнения скриптов (как с этим бороться, описано в статье Стыкуем асинхронные модули). Если у вас есть несколько скриптов, которые зависят друг от друга, то вам нужно объединить их все вместе или использовать другой подход. Если вы используете внутренний код на странице, который зависит от внешнего, то надо каким-то образом их синхронизовать (один из вариантов такой синхронизации реализован в Web Optimizer). Я называю такой подход «стыковкой» (coupling) и привел несколько путей для его реализации в заметке Стыкуем асинхронные скрипты.