Опубликован: 16.06.2010 | Уровень: специалист | Доступ: платный
Лекция 6:

Оптимизация структуры веб-страниц

< Лекция 5 || Лекция 6: 123456 || Лекция 7 >

5.4. Стыкуем асинхронные скрипты

Этот раздел написан под впечатлением от статьи Steve Souders (автора знаменитых советов Yahoo! касательно клиентской производительности) "CoupLing Async Scripts" (http://www.stevesouders.com/bLog/ 2008/12/27/coupLing-async-scripts/). Steve проанализировал поведение JavaScript-файлов при загрузке и предложил несколько путей для обхода их "блокирующих" свойств.


Если скрипты загружаются в обычном порядке ( <script src="..."> ), то они блокируют загрузку всех остальных компонентов страницы (в последних версиях Firefox и в Safari это не так, но речь идет в основном про 70% пользователей с IE) и блокируют отрисовку всей той части страницы, которая расположена ниже вызова скриптов по HTML-коду. Асинхронная загрузка скриптов (например, при помощи динамического создания объектов после срабатывания комбинированного события window.onload) предотвращает такое поведение браузера, что ускоряет загрузку страницы.

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

Существует несколько стандартных путей для стыковки асинхронно загружаемых скриптов с другим JavaScript-кодом.

  • window onload. Выполнение внутреннего JavaScript-кода может быть привязано к событию window onload. Это очень просто в использовании, но часть скриптов может быть выполнена раньше.
  • onreadystatechange у скрипта. Внутренний код может быть привязан к событию onreadystatechange и(или) onload (необходимо будет использовать оба варианта, чтобы покрыть все популярные браузеры). В этом случае кода будет больше, он будет более сложным, но будет гарантия, что он исполнится сразу после загрузки соответствующих внешних файлов.
  • Встроенные вызовы. Внешние скрипты могут быть модифицированы таким образом, чтобы включать в самом конце вызов небольшого участка кода, который вызовет соответствующую функцию из внутреннего кода. Это все замечательно, если внешние и внутренние скрипты разрабатываются одной и той же командой. Но в случае использования сторонних разработок это не обеспечит всей необходимой гибкости для связки внешних скриптов с внутренним кодом.

В этом разделе параллельно освещаются два вопроса: как асинхронные скрипты ускоряют загрузку страницы и как можно состыковать асинхронные скрипты и внутренние, используя модифицированный вариант загрузчика от Джона Ресига (автора jQuery) — шаблон двойного тега <script>.

5.4.1. Обычные вызовы скриптов

Если добавить скрипт на страницу обычным способом (через <script src="..."> ), диаграмма загрузки будет примерно следующей.

Хотя скрипт и функционирует, но это не сделает нас намного счастливее, ибо загрузка страницы замедлится. На рис. 5.9 хорошо видно, как скрипт (по имени sorttable-async.js ) блокирует все остальные HTTP-запросы на странице (в частности, arrow-right-20x9.gif ), что замедляет загрузку страницы. Все диаграммы загрузки сняты при помощи Firebug 1.3 beta. В этой версии Firebug красной линией отображается событие onload (а синяя линия соответствует событию domcontentloaded ). Для версии с обычным вызовом скрипта событие onload срабатывает на 487-й миллисекунде.

Диаграмма загрузки скриптов в обычном случае.Источник: stevesouders.com

Рис. 5.9. Диаграмма загрузки скриптов в обычном случае.Источник: stevesouders.com

5.4.2. Асинхронная загрузка скриптов

Скрипт sorttable-async.js в данном случае не является необходимым для первоначального отображения страницы. Такая ситуация (внешние скрипты, которые не используются для первоначального отображения страницы) является кандидатом номер 1 для внедрения асинхронной загрузки. Вариант с асинхронной загрузкой скриптов подключает этот скрипт, используя DOM-методы для создания нового тега <script>:

var script = document.createElement("script"); script.src = "sorttable-async.js";
script.text = "sorttable.init()"; // это объясняется чуть ниже document.getElementsByTagName("head")[0].appendChild(script);
Листинг 5.12.

Диаграмма HTTP-загрузки для асинхронной загрузки скриптов изображена на рис. 5.10. Стоит обратить внимание, как асинхронный подход предотвращает блокирующее поведение: sorttable-async.js и arrow-right-20x9.gif загружаются параллельно. Это снижает общее время загрузки дл 429 мс.

Диаграмма загрузки скриптов в асинхронном случае, источник:stevesouders.com

Рис. 5.10. Диаграмма загрузки скриптов в асинхронном случае, источник:stevesouders.com

5.4.3. Шаблон двойного скрипта от Джона Ресига

Асинхронная загрузка скриптов позволяет ускорить загрузку страницы, но в этом случае остается, что улучшить. По умолчанию скрипт вызывает "сам себя" при помощи прикрепления sorttable.init() к обработчику события onload для этого скрипта. Некоторое улучшение производительности (и уменьшение кода) может быть достигнуто, если вызвать sorttable.init() внутри тега <script>, чтобы вызвать его сразу же после загрузки внешнего скрипта (подключенного через src).

Выше описано три способа по стыковке внутреннего кода с асинхронной загрузкой внешних скриптов: window onload, onreadystatechange у скрипта и встроенный в скрипт обработчик. Вместо всего этого можно использовать технику от Джона Ресига — шаблон двойного тега <script>. John описывает, как связать внутренние скрипты с загрузкой внешнего файла, следующим образом:

<script src="jquery.js" type="text/javascript">
jQuery("p").addClass("pretty"); </script>
Листинг 5.13.

В этом случае код внутри <script> срабатывает только после того, как загрузка и инициализация внешнего скрипта завершилась. У этого подхода по стыковке скриптов есть несколько очевидных преимуществ:

  • проще: один тег <script> вместо двух;
  • прозрачнее: связь внутреннего и внешнего кодов более очевидна;
  • безопаснее: если внешний скрипт не загрузится, внутренний код не будет выполнен, что предотвратит появление ошибок, связанных с неопределенными переменными.

Это замечательный шаблон для асинхронной загрузки внешних скриптов. Однако чтобы его использовать, нам придется внести изменения как во внутренний код, так и во внешний файл. Для внутреннего кода придется добавить уже упомянутую выше третью строку, которая выставляет свойство script.text. Для завершения процесса стыковки нужно добавить в конец sorttable-async.js:

var scripts = document.getElementsByTagName("script"); var cntr = scripts.length; while ( cntr ) {
var curScript = scripts[cntr-1];
if ( -1 != curScript.src.indexOf("sorttable-async.js") ) { eval( curScript.innerHTML ); break;
}
cntr—;
}
Листинг 5.14.

Этот код проходится по всем скриптам на странице, находит необходимый блок, который должен загрузить сам себя (в этом случае это скрипт с src, содержащим sorttable-async.js ). Затем он выполняет код, который добавлен к скрипту (в этом случае —sorttable.init()), и таким образом вызывает сам себя. (Небольшое замечание: хотя призагрузке скрипта текст в нем был добавлен при помощи свойства text, обращение к нему происходит при помощи свойства innerHTML. Это необходимо для обеспечения кроссбраузерности.) При помощи такой оптимизации мы можем загрузить внешний файл скрипта, не блокируя загрузку других ресурсов, и максимально быстро выполнить привязанный к данному скрипту внутренний код.

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

var _on_ready_execution = setInterval(function() { if (typeof urchinTracker === 'function') { urchinTracker();
clearInterval(_on_ready_execution);
}
}, 10);
Листинг 5.15.

Этот подход был уже описан в книге "Разгони свой сайт" (http://speedupyourwebsite.ru/books/speed-up-your-website/), однако он предполагает дополнительную нагрузку на процессор для постоянной проверки готовности искомого скрипта и не срабатывает в случае недоступности внешнего файла: проверка продолжает выполняться.

Однако в случае проверки по интервалу нам совсем не нужно модифицировать внешний файл, в случае же двойного использования тега <script> это просто необходимо. Проверку по интервалу можно улучшить, если по истечении некоторого времени (5—10 секунд, например) перезапускать загрузку внешнего файла (меняя исходный тег <script> при помощи уникального GET-параметра), а после нескольких неудачных перезапусков вообще прекращать загрузку (возможно, с каким-то сообщением об ошибке).

5.4.4. "Ленивая" загрузка

Общее время загрузки может быть уменьшено еще больше, если использовать "ленивую загрузку" скрипта (загружать его динамически как часть обработчика события onload ). Мы можем просто оборачивать заявленный выше код в обработчик события onload:

window.onload = function() {
var script = document.createElement("script"); script.src = "sorttable-async.js"; script.text = "sorttable.init()";
document.getElementsByTagName("head")[0].appendChild(script);
}
Листинг 5.16.

В этом случае нам жизненно необходима техника стыковки скриптов. Преимущество данного подхода заключается в том, что событие onLoad срабатывает раньше — это подтверждается на рис. 5.11. Событие onLoad, отмеченное красной линией, срабатывает примерно на 320-й миллисекунде. Стоит отметить, что в таком случае мы увеличиваем общее время загрузки страницы (за счет некоторой синхронизации загрузки скриптов с общим процессом загрузки страницы), но ускоряем появление первоначальной картинки в браузере пользователя.

Диаграмма загрузки в случае "ленивой" загрузки скриптов, источник stevesouders.com

Рис. 5.11. Диаграмма загрузки в случае "ленивой" загрузки скриптов, источник stevesouders.com

5.4.5. Заключение

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

  • обычная загрузка скриптов — 487 мс;
  • асинхронная загрузка — 429 мс;
  • "ленивая" загрузка 320 мс.

Выше показано время, после которого срабатывает событие onload. Для других веб-приложений применение асинхронной загрузки для улучше ния производительности может привести к гораздо более впечатляющим результатам и быть намного более приоритетным. В нашем случае асинхронная загрузка скриптов немного лучше (~400 мс против 417 мс). В обоих случаях нам нужно каким-то образом связывать внутренние скрипты с внешними.

< Лекция 5 || Лекция 6: 123456 || Лекция 7 >
Ольга Артёмова
Ольга Артёмова

Доброго времени суток!

Прошла курс, но почему-то диплом получить не могу, хотя курс значится завершенным, хотя обязательные два модуля пройдены. Как решить эту проблему?

Сертификация: оптимизация и продвижение web-сайтов.

Ярославй Грива
Ярославй Грива
Россия, г. Санкт-Петербург
Ёдгор Латипов
Ёдгор Латипов
Таджикистан, Кургантепа