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

Оптимизация JavaScript

Неблокирующая загрузка JavaScript

Внешние JavaScript-файлы блокируют загрузку страницы и сильно влияют на ее производительность, но существует достаточно простой выход из этой ситуации: использовать динамические теги <script> и загружать скрипты параллельно, увеличивая тем самым скорость загрузки страницы и улучшая пользовательское восприятие. Давайте сначала рассмотрим, в чем заключается проблема с загрузкой скриптов. Все сводится к тому, что браузер не может сказать, что находится внутри скрипта, не загрузив его полностью. Скрипт может содержать вызовы document.write(), которые изменяют DOM-дерево, или вообще location.href, что отправит пользователя на другую страницу. В последнем случае все компоненты, загруженные на предыдущей странице, могут оказаться ненужными. Чтобы предотвратить загрузки, которые могут оказаться лишними, браузеры сначала загружают, затем анализируют и исполняют каждый скрипт перед тем, как переходить к следующему файлу в очереди на загрузку. В результате каждый вызов скрипта на вашей странице блокирует процесс загрузки и оказывает негативное влияние на скорость загрузки.

Ниже приведена временная диаграмма, которая демонстрирует процесс загрузки медленных JavaScript-файлов. Загрузка скриптов блокирует параллельную загрузку фоновых картинок, которые идут сразу за ним:

 Временнaя диаграмма: блокирующее поведение JavaScript-файлов

Рис. 7.1. Временнaя диаграмма: блокирующее поведение JavaScript-файлов

Число загрузок с одного хоста

Над временнoй диаграммой (кроме блокирования картинок) нам также стоит задуматься о том, что картинки после скрипта загружаются только по две. Это происходит из-за ограничений на число файлов, которые могут быть загружены параллельно. В IE <= 7 и Firefox 2 можно параллельно загружать только 2 файла (согласно <ref src="http 1.1 спецификации), но и в IE8, и в FF3 это число увеличено уже до 6.

Можно обойти это, используя несколько доменов для загрузки ваших файлов, потому что ограничение работает только на загрузку двух компонентов с одного хоста (более подробно эта проблема была рассмотрена в пятой главе). Однако следует понимать, что JavaScripts блокирует загрузки со всех хостов. В действительности в приведенном выше примере скрипт располагается на другом домене, нежели картинки, однако по-прежнему блокирует их загрузку.

Если в силу каких-либо причин не удается воспользоваться преимуществами "отложенной" загрузки, то следует размещать вызовы на внешние файлы скриптов в низу страницы, прямо перед закрывающим тегом </body>. В действительности это не ускорит загрузку страницы (скрипт по-прежнему придется загрузить в общем потоке), однако поможет сделать отрисовку страницы более быстрой. Пользователи почувствуют, что страница стала быстрее, если увидят какую-то визуальную отдачу в процессе загрузки.

Неблокирующие скрипты

На самом деле, существует довольно простое решение для устранения блокировки загрузки: нам нужно добавлять скрипты динамически, используя DOM-методы. Это как? Попробуем создать новый элемент <script> и прикрепить его к <head>:

var js = document.createElement('script');
js.src = 'myscript.js';
var head = document.getElementsByTagName('head')[0];
head.appendChild(js);

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

 Временная диаграмма: неблокирующее поведение JavaScript-файлов

Рис. 7.2. Временная диаграмма: неблокирующее поведение JavaScript-файлов

Как мы видим, файлы скриптов уже не блокируют загрузку, и браузер может начать работать с другими компонентами. Общее время загрузки при этом сократилось вдвое. Это будет работать только в том случае, если "динамические" скрипты не содержат вызовов document.write. Если это не так, то все такие вызовы нужно будет заменить на element.inner HTML либо отказаться от использования этой техники.

Зависимости

Еще одна проблема, которая связана с динамическим подключением скриптов, заключается в разрешении зависимостей. Предположим, что у вас есть 3 скрипта и для three.js требуется функция из one.js. Как вы гарантируете работоспособность в этом случае?

Наиболее простым способом является объединение всех скриптов в одном файле. Это не только избавит нас от самой проблемы, но и улучшит производительность страницы, уменьшив число <ref src="http-запросов. Увеличения производительности может и не произойти в случае кэширования отдельных файлов и повторного посещения, но от описанной проблемы это точно вылечит.

Если все же приходится использовать несколько файлов, то можно добавить на подгружаемый тег обработчик события onload (это будет работать в Firefox) и onreadystatechange (это будет работать в IE). Наиболее кроссбраузерным подходом будет использование меток внутри каждого файла (например, {filename}_loaded ) и проверка их с некоторым интервалом на соответствие true. Это обеспечит выполнение зависимостей.

Как показали исследования динамической загрузки скриптов, проблемы с зависимостями были отмечены на IE6- и Safari3 (под Windows). Из 10 скриптов, которые загружались параллельно (на самом деле максимум они загружались по 6 в FF3, это связано с внутренними ограничениями браузера на число одновременных соединений с одним хостом), все 10 срабатывали в случайном порядке, начиная с 3-5, как раз в этих браузерах. В других браузерах (Opera 9.5, Firefox 2, Firefox 3, Internet Explorer 8) такого поведения отмечено не было.

Вопрос о динамической загрузке таблиц стилей был подробно рассмотрен в четвертой главе (где речь шла о технике объединения картинок и последовательном отображении страницы в случае большого размера CSS-файлов). В случае комбинированной загрузки компоненты надо расположить в порядке приоритетности, потому что браузеры не смогут загрузить все сразу. А пользователи могут попытаться поработать с некоторыми "отложенными" возможностями максимально быстро, что и нужно предвосхитить.

А если по-другому?

Ниже приведено сравнение других методов для снятия блокировки с загрузки скриптов, но все они также обладают своими недостатками.

Таблица 7.1. Сравнение методов "отложенной" загрузки JavaScript-файлов
Метод Недостатки
Используем атрибут defer тега script Работает только в IE
Используем document.write() для подключения тега script
  1. Неблокирующее поведение возможно только в IE (через defer )
  2. Не рекомендуется широко использовать document.write
Используем XML<ref src="httpRequest для получения тела скрипта, затем исполняем его через eval() "evil() - зло" (долго выполняется, есть потенциальная угроза взлома при передаче "неправильных" данных)
Используем XHR-запрос для получения тела скрипта, затем создаем новый тег script и устанавливаем его содержаниe Еще сложнее, чем предыдущий случай
Загрузка скрипта в iframe
  1. Сложно
  2. Издержки на создание iframe
Дарья Билялова
Дарья Билялова
Россия
Елена Петрушевская
Елена Петрушевская
Россия, г. Нижневартовск