Поводом к написанию этого поста послужило то, что не раз мне приходилось замечать, что вставка на страницу кода кнопок различных сервисов (например: вконтакте, фейсбук, твиттер, одноклассники) приводила к заметному замедлению загрузки и отображения страницы. Речь идет о том случае, когда используется подключение внешних javascript этих социальный сервисов.
Если мы используем простые статичные графические кнопки, никаких проблем нет, т.к. это минимум графики и скриптов, которые расположены локально (можно посмотреть пример реализации тут http://pervushin.com/social-button-for-blog.html). Но мы видим только иконки соц. сервисов, никакой статистики (сколько нашу страницу "залайкнули") нет. Т.е. если мы хотим видеть и статистику, то придется подключать внешние скрипты. И тут стоит иметь в виду, что сколько таких кнопок мы подключили, то столько внешних скриптов браузер вынужден скачать, т.е. это дополнительные подключения к внешним серверам.

Чтобы показать, что происходит, если на странице есть скрипты в секции , предлагаю рассмотреть ряд тестовых примеров. Я буду использовать FireFox 3.6 и FireBug.

Итак:
1) Простейшая страница с одним файлом стилей, двумя скриптами и тремя картинками:













А вот диаграмма загрузки для нее:

Обратите внимание, что все картинки грузятся только после загрузки самого "долгого" javascript файла.
Я намеренно сделал довольно длительной загрузку dummy_css.css и dummy_js.js. Это просто два файла:

dummy_css.php

html, body {
margin:0;
padding:0;
}
.img_container{
margin:0 auto;width:500px;
}

dummy_js.php


var param=1;

Итак, видно что js файл блокирует загрузку всей остальной графики.

2) Все почти то же самое, но dummy_ js. js грузится с внешнего хоста:

Ситуация аналогична предыдущей:

3) Попробуем поменять в секции head местами css и js файлы (css теперь идет после js):







Смотрим на диаграмму загрузки:

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

4) Увеличим по времени загрузку css до 4-х секунд (html код как в случае N3):

5) Еще один интересный случай: css располагается до js, но css грузится дольше















Тут уже css блокирует загрузку картинок...

6) Переносим один js внутрь < body>
















Видно, что dummy_ js. js блокирует загрузку только третьей картинки, которая расположена в html коде после него. Но если css грузится дольше, то тогда уже он будет блокировать загрузку графики. Нетрудно представить, что подключаемые внешние скрипты могут сильно замедлить загрузку и отрисовку страницы, особенно если удаленный сервер по каким-то причинам долго отвечает.

Размещение внешних скриптов перед

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

Но есть еще одна проблема, поясню на примере:




$("img").click(function() {
alert($(this).attr("src"));
});
});






Если js перед будет грузиться долго, то клики по картинкам до полной загрузки этого скрипта ни к чему не приведут, т.к. $(document).ready() сработает только после полной загрузки js. Так что если на страницах есть некая логика, которая предусматривает обработку событий, то этот способ мало подходит.

Итак, что нужен способ неблокирующей загрузки скриптов...

Создаем async.js:



script.src = "dummy_js.js";


и подключим его:











$(document).ready(function() {
$("img").click(function() {
alert($(this).attr("src"));
});
});






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

Но если в коде все-таки удобнее размещать async.js именно в , то следует немного поменять содержимое async.js:

$(document).ready(function() {
var script = document.createElement("script");
script.src = "dummy_js.js";
document.getElementsByTagName("head") .appendChild(script);
}
);

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

Сейчас я расскажу Вам об интересном способе, который поможет вам ускорить свой сайт на WordPress за счет параллельной загрузки скриптов.

Для чего это нужно?

Все очень просто. Современный сайт представляет из себя сборник самых разнообразных скриптов, тегов, сниппетов, стилей, шрифтов и всего прочего. Как вы понимаете, чем больше «плюшек», тем дольше грузится ваш сайт. Что касается JavaScript, тут отдельная песня. Замечали ли вы такой косяк, когда страница вроде бы загрузилась, но вкладка показывает, что страница все еще грузится? Так бывает, когда какой-то отдельный скрипт не прогрузился до конца. Ладно бы так, иногда страница вообще простаивает до тех пор, пока не загрузится тот самый, вроде бы и не совсем важный скрипт. А у ваших пользователей просто может не хватить терпения.

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

Асинхронный вызов скрипта выглядит так:

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

Как автоматизировать процесс?

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

Находим уже знакомый нам файл functions.php вашей темы и вставляем туда (например в конец) следующий код:

// асинхронный javascript function wcs_defer_javascripts ($url) { if (FALSE === strpos($url, ".js")) return $url; if (strpos($url, "jquery.js")) return $url; return "$url" async="async"; } add_filter("clean_url", "wcs_defer_javascripts", 11, 1);

Заключение

Что можно добавить в заключение. Данный скрипт, возможно, подойдет не всем, так-как, кто его знает, какие скрипты подключены именно у вас, поэтому, ставьте и экспериментируйте. Минуса у такого скрипта быть не может, разве что несовместимость с конкретным сайтом ввиду его специфики. Это был еще один маленький шаг по большой оптимизации вашего сайта.

Асинхронность в javascript — представляет собой правило в соответствии с которым блоки JS кода обрабатываются браузером параллельно с загрузкой DOM — т.е. структуры веб-страницы или после загрузки DOM.

Код на JS, как известно, помещается между тегами . При этом код может содержаться как в HEAD документа, так и в BODY — при этом в любой части документа. Также javascript можно подгружать с других доменов.

Обычно JS помещают в конце страницы именно из тех соображений, что на обработку кода уходит определенное время — загрузка страницы при этом приостанавливается — DOM не может загрузиться поскольку ждет завершения исполнения скритпа на javascript.

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

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

Чтобы исправить эту ситуацию предусмотрена опция async , позволяющая загружать JS асинхронно. Структура документа при использовании документа и все элементы страницы не будут ждать завершения исполнения скрипта.

Скрипт инициируется, страница продолжит загружаться даже если скрипт не завершил свою работу. Какой-то функционал страницы при этом может работать некорректно (если в javascript допущены ошибки) — в целом же страница загружается и посетитель сайта может с ней работать.

Стоит отметить, что async не поддерживается некоторыми версиями Internet Explorer, но поскольку пользуется им очень маленькое количество пользователей — обычно имеет смысл этим пренебречь и использовать асинхронность в javascript.

Альтернативой async может быть defer . При использовании данного атрибута скрипт будет исполняться только после того как загружен DOM. Директива поддерживается всеми браузерами, ее особенность в том, что она обрабатывает JS скрипты в том порядке, в котором они подключаются.

Это может быть как плюсом, так и минусом. Если порядок выполнения скриптов принципиален — defer — лучшее решение.async можно принудительно отключить: script.async=false;

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

Примечание: ниже расположен перевод статьи от Steve Souders (автора знаменитых советов Yahoo! касательно клиентской производительности) "Coupling async scripts" . Steve анализирует поведение JavaScript-файлов при загрузке и предлагает несколько путей для обхода их «блокирующих» свойств. Мои комментарии далее курсивом.

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

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

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

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

В этой заметке я параллельно (никакого каламбура!) освещаю два вопроса: как асинхронные скрипты ускоряют загрузку страницы и как можно состыковать асинхронные скрипты и внутренние используя модифицированный вариант загрузчика от John Resig (автора jQuery ) — шаблон двойного тэга script . В качестве иллюстрации этого можно привести мою недавнюю работу по сортировке результатов UA Profiler . Я выполнил ее при помощи скрипта сортировочного скрипта от Stuart Langridge. Примерно за 5 минут я смог добавить его скрипт на страницу для сортировки таблицы с результатами. Затратив немного больше времени, я смог сделать загрузку этого скрипта асинхронной и ускорить загрузку страницы более чем на 30%, используя технику стыковки асинхронных скриптов.

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

Изначально я добавил сортирующий скрипт Stuart Langridge на страницу UA Profiler обычным способом (через ), это можно увидеть на варианте с обычным вызовом скрипта . Диаграмма загрузки изображена на рис. 1.

Рис. 1. Диаграмма загрузки скриптов в обычном случае.

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

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

var script = document.createElement("script"); script.src = "sorttable-async.js"; script.text = "sorttable.init()"; // это объясняется в следующем разделе document.getElementsByTagName("head").appendChild(script);

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

Рис. 2. Диаграмма загрузки скриптов в асинхронном случае

Шаблон двойного скрипта от John Resig

Позволяет укорить загрузку страницы, но в этом случае остается, что улучшить. По умолчанию сортирующий скрипт вызывает «сам себя» при помощи прикрепления sorttable.init() в обработчику события onload для этого скрипта. Некоторое улучшения производительности (и уменьшение кода ) может быть достигнуто, если вызвать sorttable.init() внутри тега script , чтобы вызвать его сразу же после загрузки внешнего скрипта (подключенного через src ). В этом случае в качестве "API" я использую одну-единственную функцию, но я предполагаю, что этот случай иллюстрирует максимально расширяемый шаблон, позволяющий использовать внешний модуль безо всяких предположений о его дальнейшем использовании (классическая ситуация использования JavaScript-логики от внешних разработчиков ).

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

jQuery("p").addClass("pretty");

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

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

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

var scripts = document.getElementsByTagName("script"); var cntr = scripts.length; while (cntr) { var curScript = scripts; if (-1 != curScript.src.indexOf("sorttable-async.js")) { eval(curScript.innerHTML); break; } cntr--; }

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

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

var _on_ready_execution = setInterval(function() { if (typeof urchinTracker === function) { urchinTracker(); clearInterval(_on_ready_execution); } }, 10);

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

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

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

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