Асинхронная загрузка скриптов. Составляем тестовый проект. Поддержка и современные браузерные движки

Иногда возникает необходимость в поэтапной асинхронной загрузке данных. И это может быть все, что угодно. Начиная от загрузки зависимых справочников (например, страна -> города), заканчивая объединением и обработкой информации с различных источников (например, вы используете несколько разных сервисов, и каждый из них тратит разное время на формирование данных).

А теперь чуть попроще.

Проблематика поэтапной асинхронной загрузки данных в jQuery (ajax)

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

// Первый запрос $.ajax(url, { ... success: function (result) { // Какой то код... // Вызов следующего обработчика nextLoader(); } });

А теперь, допустим, перед вами стоит чуть более сложная задача. Например, вам необходимо запустить функцию только после того, как выполнятся несколько асинхронных запросов (например, получение пузомерок для сайта). Конечно, их так же можно связать в прямую цепочку, как это было показано выше. Но, при этом начинает теряться сама суть асинхронности. И так же может получиться, что вы строите зависимость между совершенно не связанными блоками. Согласитесь, что показатель тИЦ и данные по whois не совсем зависят друг от друга.

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

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

Согласитесь, что организовать вызов всех функций в одну цепочку - ни только не простое занятие, но еще и не очень приятное. Кроме этого еще представьте, что вам надо добавить несколько полей и различных обработчиков. Что будет? У вас начнет голова закипать от поиска нужных мест "куда вставить". Тут уже нужен какой-то механизм, который позволит вам выстроить все запуски поэтапно.

Пишем скрипт для организации поэтапной асинхронной загрузки в jQuery

Первым делом необходимо определиться с вполне логичным вопросом "почему не использовать готовые библиотеки?". Как такового, ответа на этот вопрос не существует. Все очень сильно зависит от задачи. Так как слово "библиотека" обычно подразумевает:

  • отсутствие управляемости кода (вы не можете поменять внутреннюю логику библиотеки; а если вы это сделаете, то любое изменение может сильно "аукнуться" в будущем)
  • необходимость изучения ее базовый свойств и принципов (обычно, это сводится к прочтению всей возможной документации и куче различных тестов)
  • ограничения на версии jQuery (если используются сложные ядровые механизмы, то возможно привязка к версии библиотек jQuery)
  • возможные проблемы совместимости с другими скриптами (далеко не факт, что библиотека будет корректно себя вести с другими библиотеками)
  • чрезмерное богатство функциональности, и как следствие наличие "дополнительных ограничений"
  • и т.д.

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

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

Если вы ответили себе, что хотите использовать библиотеки, то, как вариант, можете скачать уже готовый тестовый пример с готовым скриптом в конце статьи.

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

Составляем требования к скрипту

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

  • Подключение и настройка скрипта должны происходить автоматически (вам необходимо подключить лишь файл js)
  • Повторное подключение файла скрипта не должно вызывать сбой
  • Использовать только стандартные механизмы jQuery
  • Интерфейс должен быть простым и понятным
  • Вы можете составлять несколько независимых цепочек этапов
  • У вас всегда должна быть возможность быстро подкорректировать действия скрипта
  • Скрипт не должен накладывать сложные ограничения на ваш код (максимально - необходимость в добавлении функции оповещения о своем выполнении)

Примечание : Всегда формируйте требования так, чтобы при реализации не начать заниматься тем, что вообще не требуется. И обязательно включайте в него "итак понятные" пункты, чтобы эти пункты и дальше оставались "итак понятными".

Составляем тестовый проект

Структура проекта будет выглядеть следующим образом:

  • css - каталог со стилями
    • template.css - стили тестового проекта
  • images - каталог с картинками (для визуального эффекта)
    • ajax-loader.gif - картинка загрузки
  • js - каталог со скриптами
    • jquery-ajax-stage.js - скрипт для поэтапной загрузки
  • data.json - тестовые данные
  • index.html - начальная страница

Примечание : При составлении структуры тестового проекта, старайтесь максимально приблизить ее к структуре настоящего проекта. Это поможет вам отловить проблемы, связанные со структурой, еще на этапе разработке.

Файл с тестовыми данными - data.json

Этот файл нужен только для того, чтобы можно было убедиться, что загрузка данных через ajax никак не повлияет. Вы можете наполнить его любым json. Например, так:

{ data: "Много данных" }

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

Файл стилей - template.css

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

Left-table { float: left; padding-right: 10px; } .right-table { float: left; padding-right: 10px; } table.table-loader { border-spacing: 0px; border-collapse: collapse; width: 300px; } table.table-loader tr td { padding: 5px; border: 1px solid #ccc; }

Файл скрипта для поэтапной асинхронной загрузки - jquery-ajax-stage.js

Сама реализация поэтапной асинхронной загрузки.

(function (parentObject) { // Защита от повторного определения if(parentObject.eventManager) return; // Определяем объект parentObject.eventManager = { // Добавляем этап addStage: function (el, stageName) { var elCount = $(el).length; // Проверяем корректность параметров if(elCount > 0 && !!stageName) { // Такой этап уже есть if (($(el).get(0).eventStages = $(el).get(0).eventStages || {})) return; // Определяем "nfg $(el).get(0).eventStages = { waiterCount: 0, // Счетчик объектов находящихся в состоянии ожидания данных onEvent: // в данном массиве будут хранится все функции для вызова }; } }, // Удаляем этап removeStage: function (el, stageName) { var elCount = $(el).length; // Проверяем корректность параметров if(elCount > 0 && !!stageName) { // Такой этап нашелся if (($(el).get(0).eventStages = $(el).get(0).eventStages || {})) { delete ($(el).get(0).eventStages = $(el).get(0).eventStages || {}); ($(el).get(0).eventStages = $(el).get(0).eventStages || {}) = null; } } }, // Увеличиваем счетчик выполняемых функций для этапа addStageWaiter: function (el, stageName) { var elCount = $(el).length, stage = ($(el).get(0).eventStages = $(el).get(0).eventStages || {}); // Проверяем корректность параметров if(elCount > 0 && !!stageName && stage) { // Увеличиваем счетчик загрузок stage.waiterCount++; } }, // Уменьшаем счетчик выполняемых функций для этапа // Т.е. оповещаем, что функция выполнилась removeStageWaiter: function (el, stageName) { var elCount = $(el).length, stage = ($(el).get(0).eventStages = $(el).get(0).eventStages || {}); // Проверяем корректность параметров if(elCount > 0 && !!stageName && stage) { // Уменьшаем счетчик загрузок stage.waiterCount--; // Проверяем состояние этапа this.checkStage(el, stageName); } }, // Проверяем состояние этапа checkStage: function (el, stageName) { var elCount = $(el).length, stage = ($(el).get(0).eventStages = $(el).get(0).eventStages || {}); // Проверяем корректность параметров if(elCount > 0 && !!stageName && stage) { if (stage.waiterCount 0) { // Очередь FIFO - первый вошел, первым вышел, как в магазине stage.onEvent.shift()(); } } } }, // Добавляем вызов функции, который запустится при выполнении всего этапа onLoadStage: function (el, stageName, funcHandler) { var elCount = $(el).length, stage = ($(el).get(0).eventStages = $(el).get(0).eventStages || {}); // Проверяем корректность параметров if(elCount > 0 && !!stageName && stage && typeof (funcHandler) === "function") { // Добавляем обработчик stage.onEvent.push(funcHandler); // Проверяем состояние этапа this.checkStage(el, stageName); } } }; })(window);

Тестовая страница - index.html

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

Подключаем все необходимые файлы:

Составляем пару тестовых блоков. В блок входит кнопка для запуска и таблица куда поэтапно будут записываться данные.

Начать загрузку

Определим функцию, которая будет создавать тестовые функции для этапов:

function getFakeLoader(storage, currStage, startDate, selector) { return function () { setTimeout(function () { $.ajax("data.json?callback=?", { contentType: "text/plain; charset=utf-8", dataType: "jsonp", jsonpCallback: function (result) { $(selector).html("Время от начала: " + ((+new Date() - startDate)/1000.0).toFixed(2) + " секунд"); // Пишем время загрузки window.eventManager.removeStageWaiter(storage, currStage); // Уменьшаем счетчик } }); }, Math.random() * (3000) + 1000 // Задежрка от 1 до 4 секунд); }; }

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

function formTestFor(selector) { $(selector + " .start-process").click(function () { var startDate = new Date(); // Для красоты добавим картинки setLoaderImgs(selector); // Определим этапы window.eventManager.addStage($(selector + " .table-loader"), "1"); window.eventManager.addStage($(selector + " .table-loader"), "2"); window.eventManager.addStage($(selector + " .table-loader"), "3"); // Заблокируем кнопку до конца этапа $(selector + " .start-process").attr("disabled", "disabled"); // Наполним очередь ожидания по 3 загрузки на стейдж // По факту эти действия должны происходить в местах запуска функций этапов window.eventManager.addStageWaiter($(selector + " .table-loader"), "1"); ... window.eventManager.addStageWaiter($(selector + " .table-loader"), "3"); // Теперь создадим в обратном порядке (для наглядности) функции загрузки // По факту эти действия должны происходить в местах определения функций window.eventManager.onLoadStage($(selector + " .table-loader"), "2", getFakeLoader($(selector + " .table-loader"), "3", startDate, selector + " .row-3 .td-1")); ... window.eventManager.onLoadStage($(selector + " .table-loader"), "1", getFakeLoader($(selector + " .table-loader"), "2", startDate, selector + " .row-2 .td-3")); // Добавим обычный текстовый вывод готовых этапов window.eventManager.onLoadStage($(selector + " .table-loader"), "1", function () { $(selector + " .row-1-end td").html("Первый этап закочнился за " + ((+new Date() - startDate)/1000.0).toFixed(2) + " секунд. Начинается следующий этап."); }); ... window.eventManager.onLoadStage($(selector + " .table-loader"), "3", function () { $(selector + " .row-3-end td").html("Третий этап закочнился за " + ((+new Date() - startDate)/1000.0).toFixed(2) + " секунд.
Загрузка окончена"); }); // После окончания третьего этапа разблокируем кнопку onLoadStage window.eventManager.onLoadStage($(selector + " .table-loader"), "3", function () { // Разблокируем кнопку $(selector + " .start-process").attr("disabled", null); }); // Теперь запустим функции первого этапа getFakeLoader($(selector + " .table-loader"), "1", startDate, selector + " .row-1 .td-1")(); getFakeLoader($(selector + " .table-loader"), "1", startDate, selector + " .row-1 .td-2")(); getFakeLoader($(selector + " .table-loader"), "1", startDate, selector + " .row-1 .td-3")(); // Наблюдаем... }); }

Теперь собираем все файлы в проект и переходим к первичному тестированию и демонстрации.

Смотрим результат

Открываем файл index.html в браузере. Должен отобразиться следующий интерфейс:

Как видно, нам доступно две кнопки для запуска параллельных процессов. После нажатия на них, должна начаться загрузка для соответствующих блоков. И выглядеть это будет примерно так:

После того, как оба процесса закончили свое выполнение, смотрим на время и убеждаемся, что этапы происходили в нужном порядке:

Закончив первичное тестирование, переходим к проверке требований:

  • Подключение и настройка скрипта должны происходить автоматически (вам необходимо подключить лишь файл js) - Есть
  • Повторное подключение файла скрипта не должно вызывать сбой - Есть
  • Использовать только стандартные механизмы jQuery - Есть (использовался только функционал селекторов)
  • Интерфейс должен быть простым и понятным - Есть
  • Вы можете составлять несколько независимых цепочек этапов - Есть
  • У вас всегда должна быть возможность быстро подкорректировать действия скрипта - Есть (скрипт составлен достаточно просто; основную часть составляют проверки входных данных)
  • Скрипт не должен накладывать сложные ограничения на ваш код (максимально - необходимость в добавлении функции оповещения о своем выполнении) - Есть (внутри функций getFakeLoader вызывается только функция для оповещения о своем завершении)

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

Приветствую, друзья! Знаете ли Вы, что загрузка JavaScript является одним из самых узких мест в производительности сайта? Сегодня моя основная задача — объяснить что такое скрипта и каким образом она влияет на быстродействие и производительность сайта.

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

  • конфигурации ,
  • скорости интернет-соединения,
  • размера файла и других…

По этой причине анализатор скорости загрузки сайта Google PageSpeed Insights рекомендует удалить из верхней части страницы код JavaScript, блокирующий ее отображение. Хорошей практикой является размещение скриптов в нижней части сайта, например, перед закрывающим тегом или настройка асинхронной загрузки.

Если код скрипта влияет на отображение верхней части сайта — не выносите его в отдельный файл, а встраивайте непосредственно в HTML.

JS может изменить содержимое сайта и даже перенаправить на другой URL-адрес. В таком случае подключение скрипта в конце документа приведет к эффекту «подергивания» страницы, подгружая новые или изменяя существующие элементы в верхней части.

Применение атрибутов async и defer для тега script

Давайте разберемся, что из себя представляет асинхронная и отложенная работа JavaScript и какая принципиальная разница между атрибутами async и defer. Но вначале рассмотрим последовательность обработки документа при обычном подключении тега script .

1 < src = "example.js" >

В наглядном примере я буду использовать следующие условные обозначения:

— обработка страницы
— загрузка скрипта
— выполнение скрипта

Таким образом последовательность обработки происходит по следующей схеме:

Разбор HTML-кода прерывается на время загрузки и выполнения скрипта, после чего продолжается. Отображение веб-страницы происходит с задержкой.

Атрибут defer

Атрибут defer позволяет браузеру начать загрузку js-файлов параллельно, не останавливая дальнейшую обработку страницы. Их выполнение происходит после полного разбора объектной модель документа (от англ. Document Object Model, сокращенно DOM), при этом браузер гарантирует последовательность на основе порядка подключения файлов.

1 < defer src = "example.js" >

Атрибут async

Поддержка атрибута async появилась в HTML5, он разрешает браузеру загружать js-файлы параллельно и выполнять сразу после загрузки, не дожидаясь обработки остальной части страницы.

1 < async src = "example.js" >

Схема последовательности обработки:

Это асинхронная загрузка. Атрибут рекомендуется применять для таких скриптов, которые не оказывают значительного влияния на отображение документа. К ним относятся счетчики сбора статистики (Google Analytics, Яндекс Метрика), коды рекламных сетей (Рекламная сеть Яндекса, Google AdSense), кнопки социальных сетей и так далее.

Скорость загрузки сайта — один из факторов ранжирования в Google.

Асинхронное подключение JavaScript снижает время загрузки страниц за счет отсутствия задержки. Наряду с этим я рекомендую сжимать и объединять js-файлы в один, например, с помощью библиотеки . Пользователям нравятся быстрые сайты 😎

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

Когда нечитаемый JS находится в начале страницы посетитель может вообще ничего не увидеть.

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

К сожалению, не все рекламные сети предоставляют возможность асинхронной загрузки скриптов. Поэтому в этой статье я расскажу как исправить синхронный код загрузки на асинхронный. Если владелец это скрипта не предоставляет такой возможности.

Стандартная синхронная загрузка JS

Обычно вызов скрипта с внешнего сервера выглядит так:

Асинхронная загрузка скрипта как это делает Google/Adsense

Идею я подсмотрел у Google/Adsense. Чтобы скрипт загружался асинхронно от остального HTML кода необходимо в код вызова добавить async .

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

Как видите все просто. Правда работать это будет только в браузерах которые поддерживают стандарт HTML5. На момент написания этой статьи таких браузеров абсолютное большинство.

Предложенный вариант не является универсальным на 100%. Многие скрипты после внесения указанных изменений просто перестают работать. По отзывам в сети, это способ не работает если в скрипте используется элемент document.write .

Надежный вариант асинхронной загрузки

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

В том месте страницы, где нужно отобразить результат работы JavaScript нужно создать пустой div блок:

Затем в конце страницы перед закрывающим тегом BODY вставляем скрипт для асинхронной загрузки:

// JavaScript который надо загрузить асинхронно // переместить его в реальную позицию отображения document.getElementById("script_block_0").appendChild(document.getElementById("script_ad_0")); // показать document.getElementById("script_ad_0").style.display = "block";

Если рекламных блоков несколько, то для каждого из них надо все повторить, создавая уникальные индивидуальные DIV блоки. Не забывайте менять имена классов и ID блоков DIV. В моем примере достаточно сменить цифру ноль, в новом блоке ее нужно заменить на 1 и так далее.

Этот вариант более сложный, но работает везде кроме очень древних браузеров таких как Internet Explorer 6. Который,к счастью, уже практически не встречается на компьютерах пользователей.

) я писала о том, какой эффект оказывают JavaScript-файлы на Критический Путь Рендеринга(CRP).


JavaScript является блокирующим ресурсом для парсера. Это означает, что JavaScript блокирует разбор самого HTML-документа. Когда парсер доходит до тега <script> (не важно внутренний он или внешний), он останавливается, забирает файл (если он внешний) и запускает его.

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


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

Нормальное выполнение

Прежде чем понять разницу между этими двумя атрибутами, давайте посмотрим что происходит в их отсутствие. Как было сказано ранее, по умолчанию файлы JavaScript прерывают парсинг HTML-документа до тех пор, пока не будут получены и выполнены.
Возьмём пример, в котором элемент расположен где-то в середине страницы:


... ... ....

Вот что произойдёт, когда парсер будет обрабатывать документ:

Парсинг HTML приостанавливается, пока скрипт не будет загружен и выполнен, тем самым увеличивая количество времени до первой отрисовки.

Атрибут async

Async используется для того, чтобы указать браузеру, что скрипт может быть выполнен асинхронно.
Парсеру HTML нет необходимости останавливаться, когда он достигает тега для загрузки и выполнении. Выполнение может произойти после того, как скрипт будет получен параллельно с разбором документа.



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



Атрибут defer

Атрибут defer указывает браузеру, что скрипт должен быть выполнен после того, как HTML-документ будет полностью разобран.



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



Асинхронное, отложенное или нормальное выполнение?

Итак, когда же следует использовать асинхронное, отложенное или нормальное выполнение JavaScript? Как всегда, это зависит от ситуации и существуют несколько вопросов, которые помогут принять вам правильное решение.

Где расположен элемент ?

Асинхронное и отложенное выполнения наиболее важны, когда элемент не находится в самом конце документа. HTML-документы парсятся по порядку, с открытия до его закрытия. Если внешний JavaScript-файл размещается непосредственно перед закрывающим тегом , то использование async и defer становится менее уместным, так как парсер к тому времени уже разберёт большую часть документа, и JavaScript-файлы уже не будут оказывать воздействие на него.

Скрипт самодостаточен?

Для файлов, которые не зависят от других файлов и/или не имеют никаких зависимостей, атрибут async будет наиболее полезен. Поскольку нам не важно, когда файл будет исполнен, асинхронная загрузка - наиболее подходящий вариант.

Полагается ли скрипт на полностью разобранный DOM?

Во многих случаях файл скрипта содержит функции, взаимодействующие с DOM. Или, возможно, существует зависимость от другого файла на странице. В таких случаях DOM должен быть полностью разобран, прежде чем скрипт будет выполнен. Как правило, такой файл помещается в низ страницы, чтобы убедиться, что для его работы всё было разобрано. Однако, в ситуации, когда по каким-либо причинам файл должен быть размещён в другом месте - атрибут defer может быть полезен.

Скрипт небольшой и зависим?

Наконец, если скрипт является относительно небольшим и/или зависит от других файлов, то, возможно, стоит определить его инлайново. Несмотря на то, что встроенный код блокирует разбор HTML-документа, он не должен сильно помешать, если его размер небольшой. Кроме того, если он зависит от других файлов, может понадобиться незначительная блокировка.

Поддержка и современные браузерные движки

Поддержка атрибутов async и defer очень распространена:




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

Поводом к написанию этого поста послужило то, что не раз мне приходилось замечать, что вставка на страницу кода кнопок различных сервисов (например: вконтакте, фейсбук, твиттер, одноклассники) приводила к заметному замедлению загрузки и отображения страницы. Речь идет о том случае, когда используется подключение внешних 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);
}
);

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