Продолжаем цикл статей на тему построения сайтов (веб-приложений) ориентируясь на максимальное применение AJAX технологии. Зачастую, при работе сайтов и приложений использующих AHAH требуется вместе с подгружаемым HTML контентом догружать файлы скриптов, которые не использовались ранее на страницах сайта. Одним из моментов обработки догружаемого AJAX-ом контента является выделение из получаемого контента файлов скриптов, с последующим их применением к текущему документу (DOM). Благо, на данный момент практически все основные библиотеки умеют это делать. Однако, если копнуть глубже в реализацию процесса обработки догружаемых скриптов, то текущая ситуация совсем «не радужная». Не все хорошо известные библиотеки умеют это делать хорошо, а тем более оптимально. Давайте рассмотрим указанный процесс изнутри, разберем существующие проблемы и узнаем пути их решения.
Для простоты понимания рассмотрим комплексный пример, в котором, используя AJAX, в стартовую страницу будем догружать дополнительную страницу, в которой, в свою очередь, имеются ссылки на файлы скриптов.
Итак, в стартовую страницу будем подгружать дополнительную страницу test1.html, текст которой приведен ниже
Загрузим данную страницу с помощью всем известной библиотеки jQuery. Текст стартовой страницы jquery1.html приведен ниже
Для определения проблемных мест загрузим дополнительную страницу дважды в блоки content1 и content2 соответственно.
Консоль firebug-а покажет нам примерно следующее
GET test1.html
GET test1.html
GET test1.js?_=1239115065471
GET test2.js?_=1239115067323
GET test1.js?_=1239115068456
GET test2.js?_=1239115069347
Как видно, jQuery, при асинхронной дозагрузке контента, прибавляет к адресам скриптов приставку типа _=1239115067323, для того чтобы скрипт не кешировался. Таким образом, при использовании jQuery, браузер каждый раз один и тот же скрипт на разных страницах будет загружать заново, по полному циклу, не используя кеш. Спрашивается «Зачем?» Вероятность того что для разных страниц один и тот же скрипт (с одинаковым адресом) будет разный практически = 0. Соответственно, процесс дозагрузки скриптов явно продуман не до конца. Однако, цель статьи не в том чтобы раскритиковать работу jQuery. Аналогичным образом обстоят дела и с другими библиотеками. Цель статьи в том, чтобы понять принципы и пути возможной оптимизации процесса асинхронной дозагрузки скриптов.
В данной статье, для более простого изложения и понимания материала, относительно способов и вариантов оптимизации асинхронной загрузки скриптов, будет описана реализация оптимизации рассматриваемого процесса в библиотеке Fullajax
Зачем загружать то, что уже есть в памяти и загружено на стартовой странице?
Обратите внимание на то, что скрипт test1.js уже подключен на стартовой странице, т.е. он уже есть в памяти. Соответственно достаточно легко можно получить его содержимое, без какой либо дополнительной загрузки данных с сервера. Таким образом, первое, что мы делаем – это проверяем, не загружен (подключен) ли уже скрипт в страницу стандартным путем? Если да – просто получаем его содержимое и выполняем. Выполняя указанную простую операцию, относительно нашего примера, мы сразу избавляемся от 2-х лишних запросов к серверу.
Зачем загружать повторно то, что уже однажды загружено?
Посмотрим еще раз на консоль firebug-a. Обратите внимание на порядок и количество загрузок скриптов с помощью jQuery. Скрипт test1.js и test2.js загружаются дважды каждый! Со скриптом test1.js мы разобрались на предыдущем шаге. Теперь оптимизируем загрузку второго скрипта. Скрипт test2.js достаточно загрузить один раз (первый раз), положить его содержание «за щеку» (в программный кеш) и все последующие разы доставать его оттуда, а не из сервера. Таким образом, относительно нашего примера мы избавляемся еще от одной лишней загрузки скрипта и соответственно лишнего запроса к серверу.
В сумме, на простом примере мы имеем «конкретную» оптимизацию, а именно в итоге всего 1 запрос из 4-х изначальных запросов. Вышерассмотренные шаги оптимизации работают в библиотеке Fullajax по умолчанию. Т.е. по умолчанию повторно не загружаются скрипты стартовой страницы, а также, все новые скрипты загружаются только один раз.
fullajax.ru/temp/asyncjs/fullajax1.html
Реализация оптимизации шаг №1 еще более-менее прозрачна – находим все скрипты в блоке head и получаем их содержимое. Реализация оптимизации шаг №2 требовала дополнительной синхронизации между параллельными асинхронными запросами страниц с сервера. Часто возникает ситуация когда второй параллельный запрос начался до того как закончился первый, т.е. второй запрос начался до того как мы получили текст скрипта. Соответственно, необходимо обеспечить синхронизацию между потоками, чтобы по окончанию загрузки скриптов из первого потока, об этом сразу же узнал второй поток. В общем, это более углубленная тема…
К счастью и к удивлению на этом варианты оптимизации незакончены. Мы можем еще убрать лишние «движения».
Зачем выполнять (инициализировать) повторно то, что уже однажды выполнено (инициализировано)?
В предыдущих шагах оптимизации, избавившись от повторной загрузки скриптов, мы все же продолжаем повторно исполнять одни и те же скрипты. Давайте посмотрим внутрь скриптов test1.js и test2.js.
Скрипт test1.js
Скрипт test2.js
Относительно скрипта test1.js – нам важно его исполнять каждый раз, при каждой новой загрузке, так как от этого будет зависеть значение переменной a. Тут мы ничего поделать не можем, смиримся с этим.
Относительно скрипта test2.js – повторное его выполнение не несет никакой полезной нагрузки. Функция test уже однажды определена и при каждой последующей загрузке данного скрипта, эта функция просто переопределяет сама себя. Соответственно, мы можем спокойно не осуществлять повторное выполнение данного скрипта. Таким образом, мы сэкономим процессорное время и возможно память. Однако, у нас нет подручных стандартных средств, которые позволяют выполнять скрипт только однажды (привет стандарту HTML). Для этого необходимо либо изменять внутреннюю структуру скрипта, так чтобы проводилась проверка на предыдущее выполнение, либо наделить движок (библиотеку) асинхронной загрузки скриптов соответствующим алгоритмом.
Раcсмотрим реализацию данного шага оптимизации в Fullajax. Для того чтобы осуществлять одноразовое выполнение некоторых скриптов необходимо и достаточно всего лишь добавить им «волшебный» атрибут ax:repeat='0'.
Не устали? Я немного уже «запарился» описывать… представьте сколько вышеописанное программировалось :).
И это еще не все возможные пути оптимизации, есть еще, что можно прооптимизировать при асинхронной загрузке скриптов.
Давайте рассмотрим случай, когда в подгружаемой странице имеется несколько скриптов. К примеру
jQuery в данном случае начинает загружать первый скрипт, потом по окончанию первого начинает загружать второй, потом по окончанию загрузки второго третий и т.д. Т.е. по сути, осуществляется синхронная последовательная загрузка скриптов.
fullajax.ru/temp/asyncjs/jquery2.html
И это вполне нормально! Точно так же ведут себя все браузеры при стандартной загрузке скриптов. Потому что выполнение последующего скрипта может зависеть от предыдущего. Поэтому надо дождаться выполнение первого, а затем выполнять второй.
Однако! «Выполнять» совсем не равносильно «загружать». Мы ведь можем осуществлять параллельную загрузку всех скриптов?.. А вот выполнять их будем уже последовательно, по мере их загрузки.
Таким образом, реализовывая вышеуказанный алгоритм, мы получим настоящую безопасную асихронную загрузку скриптов. Данный алгоритм оптимизации так и назван – алгоритм параллельной загрузки с последовательным применением.
fullajax.ru/temp/asyncjs/fullajax2.html
При более приближенных к реальным условиям, загружаем большие файлы скриптов.
jQuery
fullajax.ru/temp/asyncjs/jquery3.html
Fullajax
fullajax.ru/temp/asyncjs/fullajax3.html
Выводы очевидны.
Указанный алгоритм оптимизации загрузки скриптов реализован в библиотеке Fullajax и используется по умолчанию.
Иногда, при подгрузке контента с помощью AJAX, возникает необходимость отключить выполнение тех или иных скриптов, и при этом же осуществлять их нормальное выполнение при обычной стандартной загрузке. Простого стандартного способа для этого не существует. Чтобы реализовать указанную функциональность приходится на сервере определять каким образом запрашивается контент (через AJAX или стандартным путем), в соответствии с этим, добавлять или отнимать из контента страницы определенные скрипты. При использовании Fullajax существует способ намного проще – добавьте «чудесный» атрибут к стилю ax:skip='1'. Теперь библиотека будет пропускать (игнорировать) помеченные вами соответствующим образом скрипты, даже не будет пытаться загружать их.
Еще есть масса других вопросов относительно обработки скриптов в различных библиотеках. К примеру: почему jQuery сначала вставляет контент, а потом выполняет скрипты, если в подгружаемом документе четко указанно сначала выполнить скрипты, а потом вставить контент. Также есть другие моменты, которые выходят за рамки данной статьи.
В данной статье, мы с вами детально разобрали и проанализировали различные узкие места, которые возникают при асинхронной дозагрузке HTML контента со скриптами, а также рассмотрели возможные пути оптимизации таких узких мест.
Материал статьи уникальный, нигде ранее не освещался и не озвучивался, публикуется впервые. Все умозаключения и алгоритмы выведены автором статьи в процессе разработки библиотеки Fullajax.
jQuery я тоже люблю :).
UPD: — добавлены тесты при загрузке больших файлов скриптов.
Для простоты понимания рассмотрим комплексный пример, в котором, используя AJAX, в стартовую страницу будем догружать дополнительную страницу, в которой, в свою очередь, имеются ссылки на файлы скриптов.
Итак, в стартовую страницу будем подгружать дополнительную страницу test1.html, текст которой приведен ниже
<html>
<head>
<script type="text/javascript" src="test1.js"></script>
<script type="text/javascript" src="test2.js"></script>
<link type="text/css" rel="stylesheet" href="test.css"/>
</head>
<body>
Hello Bingo!
</body>
</html>
Загрузим данную страницу с помощью всем известной библиотеки jQuery. Текст стартовой страницы jquery1.html приведен ниже
<html>
<head>
<script type="text/javascript" src="test1.js"></script>
<script type="text/javascript" src="jquery-1.2.6.js"></script>
</head>
<body>
<div id="content1"></div>
<div id="content2"></div>
<script type="text/javascript">
$(function(){
$('#content1').load('test1.html')
$('#content2').load('test1.html')
})
</script>
</body>
</html>
Для определения проблемных мест загрузим дополнительную страницу дважды в блоки content1 и content2 соответственно.
Консоль firebug-а покажет нам примерно следующее
GET test1.html
GET test1.html
GET test1.js?_=1239115065471
GET test2.js?_=1239115067323
GET test1.js?_=1239115068456
GET test2.js?_=1239115069347
Как видно, jQuery, при асинхронной дозагрузке контента, прибавляет к адресам скриптов приставку типа _=1239115067323, для того чтобы скрипт не кешировался. Таким образом, при использовании jQuery, браузер каждый раз один и тот же скрипт на разных страницах будет загружать заново, по полному циклу, не используя кеш. Спрашивается «Зачем?» Вероятность того что для разных страниц один и тот же скрипт (с одинаковым адресом) будет разный практически = 0. Соответственно, процесс дозагрузки скриптов явно продуман не до конца. Однако, цель статьи не в том чтобы раскритиковать работу jQuery. Аналогичным образом обстоят дела и с другими библиотеками. Цель статьи в том, чтобы понять принципы и пути возможной оптимизации процесса асинхронной дозагрузки скриптов.
В данной статье, для более простого изложения и понимания материала, относительно способов и вариантов оптимизации асинхронной загрузки скриптов, будет описана реализация оптимизации рассматриваемого процесса в библиотеке Fullajax
Оптимизация шаг 1 – Начало у причала
Зачем загружать то, что уже есть в памяти и загружено на стартовой странице?
Обратите внимание на то, что скрипт test1.js уже подключен на стартовой странице, т.е. он уже есть в памяти. Соответственно достаточно легко можно получить его содержимое, без какой либо дополнительной загрузки данных с сервера. Таким образом, первое, что мы делаем – это проверяем, не загружен (подключен) ли уже скрипт в страницу стандартным путем? Если да – просто получаем его содержимое и выполняем. Выполняя указанную простую операцию, относительно нашего примера, мы сразу избавляемся от 2-х лишних запросов к серверу.
Оптимизация шаг 2 – Повторение мать мучения
Зачем загружать повторно то, что уже однажды загружено?
Посмотрим еще раз на консоль firebug-a. Обратите внимание на порядок и количество загрузок скриптов с помощью jQuery. Скрипт test1.js и test2.js загружаются дважды каждый! Со скриптом test1.js мы разобрались на предыдущем шаге. Теперь оптимизируем загрузку второго скрипта. Скрипт test2.js достаточно загрузить один раз (первый раз), положить его содержание «за щеку» (в программный кеш) и все последующие разы доставать его оттуда, а не из сервера. Таким образом, относительно нашего примера мы избавляемся еще от одной лишней загрузки скрипта и соответственно лишнего запроса к серверу.
В сумме, на простом примере мы имеем «конкретную» оптимизацию, а именно в итоге всего 1 запрос из 4-х изначальных запросов. Вышерассмотренные шаги оптимизации работают в библиотеке Fullajax по умолчанию. Т.е. по умолчанию повторно не загружаются скрипты стартовой страницы, а также, все новые скрипты загружаются только один раз.
fullajax.ru/temp/asyncjs/fullajax1.html
Реализация оптимизации шаг №1 еще более-менее прозрачна – находим все скрипты в блоке head и получаем их содержимое. Реализация оптимизации шаг №2 требовала дополнительной синхронизации между параллельными асинхронными запросами страниц с сервера. Часто возникает ситуация когда второй параллельный запрос начался до того как закончился первый, т.е. второй запрос начался до того как мы получили текст скрипта. Соответственно, необходимо обеспечить синхронизацию между потоками, чтобы по окончанию загрузки скриптов из первого потока, об этом сразу же узнал второй поток. В общем, это более углубленная тема…
К счастью и к удивлению на этом варианты оптимизации незакончены. Мы можем еще убрать лишние «движения».
Оптимизация шаг 3 – Переинициализация враг оптимизации
Зачем выполнять (инициализировать) повторно то, что уже однажды выполнено (инициализировано)?
В предыдущих шагах оптимизации, избавившись от повторной загрузки скриптов, мы все же продолжаем повторно исполнять одни и те же скрипты. Давайте посмотрим внутрь скриптов test1.js и test2.js.
Скрипт test1.js
var a = Math.random();
Скрипт test2.js
function test(){
alert('Hello');
}
Относительно скрипта test1.js – нам важно его исполнять каждый раз, при каждой новой загрузке, так как от этого будет зависеть значение переменной a. Тут мы ничего поделать не можем, смиримся с этим.
Относительно скрипта test2.js – повторное его выполнение не несет никакой полезной нагрузки. Функция test уже однажды определена и при каждой последующей загрузке данного скрипта, эта функция просто переопределяет сама себя. Соответственно, мы можем спокойно не осуществлять повторное выполнение данного скрипта. Таким образом, мы сэкономим процессорное время и возможно память. Однако, у нас нет подручных стандартных средств, которые позволяют выполнять скрипт только однажды (привет стандарту HTML). Для этого необходимо либо изменять внутреннюю структуру скрипта, так чтобы проводилась проверка на предыдущее выполнение, либо наделить движок (библиотеку) асинхронной загрузки скриптов соответствующим алгоритмом.
Раcсмотрим реализацию данного шага оптимизации в Fullajax. Для того чтобы осуществлять одноразовое выполнение некоторых скриптов необходимо и достаточно всего лишь добавить им «волшебный» атрибут ax:repeat='0'.
<script type="text/javascript" ax:repeat="0" src="test2.js"></script>
И все :). Далее алгоритм загрузки скриптов автоматически будет оптимизировать выполнение таких помеченных скриптов, т.е. такие скрипты будут выполняться только однажды. Определение необходимости или отсутствия необходимости повторного выполнения (инициализации) скриптов делает сам разработчик. Проанализируйте ваши скрипты! Если нет необходимости их повторного выполнения – смело помечайте их соответствующим атрибутом. В некоторых случаях, повторное выполнение отдельных скриптов даже вызывает ошибки в работе сайта (веб-приложения). Таким примером является многим известный скрипт календаря (Zapatec Calendar). Если повторно, при AJAX подгрузке контента, выполнять скрипт calendar.js из набора скриптов, календарь перестает работать и выдает ошибки.Не устали? Я немного уже «запарился» описывать… представьте сколько вышеописанное программировалось :).
И это еще не все возможные пути оптимизации, есть еще, что можно прооптимизировать при асинхронной загрузке скриптов.
Оптимизация шаг 4 – параллели песни пели
Давайте рассмотрим случай, когда в подгружаемой странице имеется несколько скриптов. К примеру
<html>
<head>
<script type="text/javascript" src="test1.js?1"></script>
<script type="text/javascript" src="test1.js?2"></script>
<script type="text/javascript" src="test1.js?3"></script>
<script type="text/javascript" src="test1.js?4"></script>
<script type="text/javascript" src="test1.js?5"></script>
<script type="text/javascript" src="test1.js?6"></script>
<script type="text/javascript" src="test1.js?7"></script>
<script type="text/javascript" src="test1.js?8"></script>
<script type="text/javascript" src="test1.js?9"></script>
<script type="text/javascript" src="test1.js?10"></script>
</head>
<body>
Hello Bingo!
</body>
</html>
jQuery в данном случае начинает загружать первый скрипт, потом по окончанию первого начинает загружать второй, потом по окончанию загрузки второго третий и т.д. Т.е. по сути, осуществляется синхронная последовательная загрузка скриптов.
fullajax.ru/temp/asyncjs/jquery2.html
И это вполне нормально! Точно так же ведут себя все браузеры при стандартной загрузке скриптов. Потому что выполнение последующего скрипта может зависеть от предыдущего. Поэтому надо дождаться выполнение первого, а затем выполнять второй.
Однако! «Выполнять» совсем не равносильно «загружать». Мы ведь можем осуществлять параллельную загрузку всех скриптов?.. А вот выполнять их будем уже последовательно, по мере их загрузки.
Таким образом, реализовывая вышеуказанный алгоритм, мы получим настоящую безопасную асихронную загрузку скриптов. Данный алгоритм оптимизации так и назван – алгоритм параллельной загрузки с последовательным применением.
fullajax.ru/temp/asyncjs/fullajax2.html
При более приближенных к реальным условиям, загружаем большие файлы скриптов.
jQuery
fullajax.ru/temp/asyncjs/jquery3.html
Fullajax
fullajax.ru/temp/asyncjs/fullajax3.html
Выводы очевидны.
Указанный алгоритм оптимизации загрузки скриптов реализован в библиотеке Fullajax и используется по умолчанию.
И еще немножко оптимизации – пропускать ~ чудеса исполнять
Иногда, при подгрузке контента с помощью AJAX, возникает необходимость отключить выполнение тех или иных скриптов, и при этом же осуществлять их нормальное выполнение при обычной стандартной загрузке. Простого стандартного способа для этого не существует. Чтобы реализовать указанную функциональность приходится на сервере определять каким образом запрашивается контент (через AJAX или стандартным путем), в соответствии с этим, добавлять или отнимать из контента страницы определенные скрипты. При использовании Fullajax существует способ намного проще – добавьте «чудесный» атрибут к стилю ax:skip='1'. Теперь библиотека будет пропускать (игнорировать) помеченные вами соответствующим образом скрипты, даже не будет пытаться загружать их.
Еще есть масса других вопросов относительно обработки скриптов в различных библиотеках. К примеру: почему jQuery сначала вставляет контент, а потом выполняет скрипты, если в подгружаемом документе четко указанно сначала выполнить скрипты, а потом вставить контент. Также есть другие моменты, которые выходят за рамки данной статьи.
В данной статье, мы с вами детально разобрали и проанализировали различные узкие места, которые возникают при асинхронной дозагрузке HTML контента со скриптами, а также рассмотрели возможные пути оптимизации таких узких мест.
Материал статьи уникальный, нигде ранее не освещался и не озвучивался, публикуется впервые. Все умозаключения и алгоритмы выведены автором статьи в процессе разработки библиотеки Fullajax.
jQuery я тоже люблю :).
UPD: — добавлены тесты при загрузке больших файлов скриптов.