Статьи

Перевод: Николай Мациевский aka sunnybear
Опубликована: 1 февраля 2008

Практический JS: разгоняем все, что движется

Примечание: ниже находится перевод статьи "Speed Up Your Javascript Load Time", в ней автор рассматривает некоторые наиболее эффективные техники и методы действия по уменьшению времени отработки JavaScript'а на клиенте. Большая часть из них общеизвестна, но в статье важно не просто их перечисление, а общий подход для решения задачи оптимизации времени загрузки. Далее мои комментарии курсивом.

JavaScript становится все популярнее и популярнее. Каждый уважающий себя вебсайт старается применить его хоть где-нибудь: будь то подгрузка динамических данных через AJAX, или же некоторые специальные (визуальные) эффекты. К несчастью, за все нужно платить: приходится использовать «тяжелые» JavaScript-библиотеки, которые добавляют к размеру вашей страницы десятки или даже сотни килобайтов кода.

Пользователи так ненавидят ждать: им подавай все и сразу. Давайте тогда рассмотрим несколько методов, которые помогут вам «причесать» ваш сайт. Здесь находятся все рабочие примеры, которые будут приведены далее.

Находим «висячие» хвосты

Как и в любой другой технике по оптимизации, нам нужно точно установить «узкие» места, которые отнимают больше всего системных ресурсов, и их исправить. (А потом снова найти такие bottleneck'и, и снова их исправить, и так до бесконечности :)). Далее приведены некоторые инструменты, которые могут помочь повысить в профилировании этого:

  1. The Firefox web-developer toolbar позволяет проанализировать размеры всех загруженных на странице файлов (клик правой кнопкой мыши > Web Developer > Information > View Document Size). Посмотрите на список файлов и оцените, что отнимает большую часть времени при загрузке страницы:

    Размер Yahoo

  2. Расширение к Firebug также позволяет посмотреть на список файлов: на вкладке "Net". Можно также отсортировать по типу файлов:

    Yslow для Firebug

  3. Также можно взглянуть на OctaGate SiteTimer, который предоставляет online-диаграмму загрузки всех файлов на странице:

    Yahoo в OctaGate

Размер вас впечатлил? Решили, что с JavaScript нужно что-то делать? Давайте этим займемся.

Сжимаем JavaScript

Для начала вам стоит сделать сами JavaScript-файлы меньше, существует множество утилит, которые позволяют «сжать» ваши файлы, убрав из них комментарии и ненужные пробелы. Подробнее о анализе сжатия JavaScript-файлов разными инструментами можно прочитать в этой статье.

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

1. Запустите JSLint (online или через локальную версию) для того, чтобы проанализировать ваш код и убедиться в том, что он корректно отформатирован.

2. Используйте один из инструментов для сжатия вашего кода (стоит им воспользоваться только в случае изначально «разреженного» кода, если JavaScript-файлы уже достаточно компактно оформлены, то лучше никакими минимизаторами их не обрабатывать).

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

Отлаживаем сжатый JavaScript

Отладка сжатого JavaScript'а может оказаться весьма непростым занятием, потому что все переменные могут быть переменованы и сама логика претерпит значительные изменения. Я бы предложил создавать «отладочную» версию вашей страницы с подключением несжатых скриптов. После того, как вы полностью протестировали и отладили рабочую версию, ее можно сжимать и отправлять на «боевой» вебсайт.

Если для тестирования вы пользуетесь автоматизированными средствами, например, jsunit, то сложностей с проверкой сжатой версии у вас не возникнет.

Боремся с рутиной

Запуск одних и тех же команд из консоли может быть весьма утомительным, и вам, скорее всего, захочется написать какой-нибудь скрипт, чтобы автоматизировать процесс. Этот .bat файл будет сжимать все .js в текущей директории в файлы с расширением .js.packed (для случая сжатия Dojo ShrinkSafe aka Rhino под Windows):

compress_js.bat:

for /F %%F in ('dir /b *.js') do java -jar custom_rhino.jar -c %%F > %%F.packed 2>&1

Естественно, если вы владеете более подходящими скриптовыми языками, например, perl'ом или bash'ем, то можете сами написать необходимый вам код.

Оптимизируем расположение JavaScript-файлов

Размещаете по возможности JavaScript в конце HTML-файлов. Обратите внимание на то, что Google Analytics и некоторые другие скрипты статистики просят размещать свои вызовы непосредственно перед закрывающим тегом </body>.

Это позволит всему остальному содержанию страниц (например, картинкам, таблицам, тексту) загрузиться и отобразиться в первую очередь. Пользователь увидит, что страница «ответила» и загрузилась. После этого «тяжелые» JavaScript-файлы могут начать загрузку в самом конце страницы.

Обычно я подключал все JavaScript-файлы одним махом в <head> страницы, но реально это абсолютно не обязательно. Там должны быть только основные (core) библиотеки, которые обязательно понадобятся при загрузке страницы. Остальные, например, визуальные эффекты для меню, разнообразная анимация и т.д., могут быть загружены позже. Ведь нашей основной задачей является повышение доступности страницы, создание видимости того, что страница отвечает и загружается очень быстро.

Загружаем JavaScript «по требованию»

Библиотека AJAX pattern создана для динамической загрузки JavaScript'а (звучит немного парадоксально: ведь сам JavaScript загружает что-либо динамически, в данном случае речь идет о рекурсивной динамике: когда JavaScript динамически загружает Javascript (который, в свою очередь, может загружать еще какой-либо JavaScript и т.д.)). Она также может подгружать скрипты в том случае, если пользователю требуется какой-либо функционал (который отсутствует при первоначальной загрузке страницы). Можно также загрузить произвольный JavaScript-файл с любого домена используя следующую функцию:

function $import(src){
    var scriptElem = document.createElement('script');
    scriptElem.setAttribute('src',src);
    scriptElem.setAttribute('type','text/javascript');
    document.getElementsByTagName('head')[0].appendChild(scriptElem);
}

// подключаем вместе со случайным GET-параметром, чтобы избежать кеширования
function $importNoCache(src){
    var ms = new Date().getTime().toString();
    var seed = "?" + ms; 
    $import(src + seed);
}

Функция $import('http://example.com/myfile.js') добавит еще один скрипт прямо в head вашего документа так, как это сделало бы его прямое включение в HTML-код. Модификация $importNoCache добавляет уникальную метку даты (timestamp) к запрашиваемому файлу, чтобы заставить браузер запросить новую версию скрипта.

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

if (myfunction){
    // функция загружена и объявлена
}
else{ // еще не загружена
    $import('http://www.example.com/myfile.js');
}

Имеется также и < a href="http://ajaxpatterns.org/On-Demand_Javascript#XMLHttpRequest-Based_On-Demand_Javascript" lang="en" rel="nofollow">AJAX-версия, но я лично предпочитаю упомянутый выше вариант, потому что она более простая и позволяет подключить файлы с любого домена.

Задерживаем выполнение JavaScript'а

Также вместо загрузки JavaScript-файлов «по требованию» (что может вызвать некоторую задержку), можно их загружать в фоновом режиме после основной загрузки. Для этого пригодится примерно следующая конструкция:

    var delay = 5;
    setTimeout("loadExtraFiles();", delay * 1000);

Она вызовет функцию loadExtraFiles() по истечению 5 секунд после загрузки этого кода на странице. В указанной функции можно загрузить все необходимые файлы, используя $import. Можно добавить функцию, которая сообщает о доступности (инициализирует) этих библиотек в самом конце их подключения (в конце подгружаемых файлов), либо вызывать ее в конце страницы, чтобы она загрузила все библиотеки самостоятельно.

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

В случае a href="http://instacalc.com" lang="en" rel="nofollow">InstaCalc на странице присутствует довольно много объемных библиотек, которые не так часто используются. На данный момент я тестируя задержку при загрузке диаграмм в течение нескольких секунд, в то время как основная функциональность доступна с самого начала. Возможно, вам потребуется изменить логику работы ваших скриптов, чтобы они смогли работать с задержками при загрузке отдельных компонентов. Как вариант стоит рассмотреть использование SetTimeout для периодического опроса статуса загрузки или добавить в каждый файл специальную функцию, которая будет извещать основную программу об окончании загрузки компонента.

Здесь более основательно рассматривается техника «отложенной» загрузки основных JavaScript-библиотек по окончанию появления DOM-объекта в браузере.

Кешируем файлы

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

Переименуйте myfile.js в myfile.js.php в добавьте следующие строки в самое его начало:

<?php 
    header("Content-type: text/javascript; charset: UTF-8");
    header("Cache-Control: must-revalidate");
    $offset = 60 * 60 * 24 * 3;
    $ExpStr = "Expires: " . 
    gmdate("D, d M Y H:i:s",
    time() + $offset) . " GMT";
    header($ExpStr);
?>

Лично мне кажется, что стоит возложить работу по кешированию на сервер (например, Apache, а не на сами серверные скрипты. Хотя бы потому, что сам сервер это будет делать быстрее и надежнее. Если добавить следующую запись в конфигурационный файл Apache, то сервер будет автоматически отдавать все файлы, имеющие расширение .js с нужными кеширующими заголовками.

<FilesMatch .*\.js$>
    ExpiresDefault "access plus 3 days"
</FilesMatch>

В данном случае время кеширования будет истекать через (60 * 60 * 24 * 3) секунд или 3 дня. Но будьте осторожны при кешировании ваших файлов, особенно, если они находятся в процессе разработки. Я советую кешировать только те файлы, которые вы не будете менять слишком часто.

Если вы все же установили время кеширования слишком большим для каких-то файлов, вы можете использовать следующий метод (для форсированной перегрузке закешированного содержания) $importNoCache, который добавляет метку времени к запрашиваемому файлу, например, myfile.js В силу того, что имя файла отличается от закешированного в браузере, последний его запросит еще раз с сервера.

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

Объединяем файлы

Я забыл упомянуть еще об одной замечательной технике, которая заключается в объединении нескольких (небольших) JavaScript-файлов в один. Браузер не может открыть более 4 соединений на один сервер (по спецификации протокола HTTP), да и определенное время тратится на установление каждого такого соединения. Поэтому в том случае, если у вас на странице подключается несколько небольших файлов, стоит объединить их в один (характерным размером в данном случае будет 10Кб: если суммарно объем всех скриптов по порядку примерно такой, то стоит их объединять, в противном случае лучше подумать над разделение функционала, сжатием и загрузкой модулей по требованию).

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

Может, стоит заархивировать?

Возможно, вы предположите, что ответом будет «Нет», по причине того что старые браузеры имеют некоторые проблемы при обработке архивированного контента. Однако, интернет не стоит не месте, а двигается все дальше и дальше вперед. Ведущие сайты, такие как Google и Yahoo уже используют архивирование, и проблемы старых браузеров не так сильно распространены, как о них любят говорить.

В результате сжатия можно выиграть до 75% размера ваших файлов. Более подробно об архивировании и минимизации JavaScript-файлов можно прочитать в следующем исследовании.

Все ли это? Продолжаем учиться

После того, как вы провели оптимизацию с помощью всех приведенных методов, стоит проверить производительность вашей страницы снова (одним из перечисленных инструментов) и почувствовать разницу до и после.

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

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

Читать дальше

Все комментарии (habrahabr.ru)