Появление AJAX сильно изменило режим работы клиентов веб-приложений. Он позволяет пользователям сосредоточиться на своей работе, не беспокоясь о частом обновлении страниц. Теоретически технология AJAX позволяет в значительной степени сократить время ожидания операций пользователя и сэкономить трафик данных в сети. Однако это не всегда так. Пользователи часто жалуются, что снижается скорость отклика систем, использующих AJAX.
Автор много лет занимался исследованиями и разработками AJAX и участвовал в разработке Dorado, относительно зрелой платформы AJAX в Китае. По опыту автора, первопричиной такого результата является не AJAX. Во многих случаях снижение скорости отклика системы вызвано непродуманным дизайном интерфейса и недостаточно эффективными навыками программирования. Ниже мы разберем несколько аспектов, на которые необходимо обратить внимание в процессе разработки AJAX.
Правильное использование клиентского программирования и удаленных вызовов процедур.
Программирование на стороне клиента в основном основано на JavaScript. JavaScript является интерпретируемым языком программирования, и его эффективность работы немного ниже, чем у Java. В то же время JavaScript работает в строго ограниченной среде, например в браузере. Поэтому разработчики должны иметь четкое представление о том, какая логика может выполняться на стороне клиента.
То, как программирование на стороне клиента следует использовать в реальных приложениях, зависит от опыта и суждений разработчика. Многие проблемы здесь можно только понять. Из-за ограниченности места здесь мы кратко резюмируем следующие меры предосторожности:
Избегайте частого использования вызовов удаленных процедур, например, избегайте использования вызовов удаленных процедур в телах циклов.
Если возможно, используйте удаленный вызов процедур AJAX (асинхронный удаленный вызов процедур).
Избегайте выполнения тяжелых операций с данными на стороне клиента. Например: большие пакеты операций копирования данных, вычисления, требующие большого объема обхода данных и т. д.
Улучшить метод работы с объектами DOM.
При программировании на стороне клиента операции с объектами DOM чаще всего занимают процессорное время. При работе с объектами DOM разница в производительности между различными методами программирования часто очень велика.
Ниже приведены три фрагмента кода с одинаковыми результатами. Их функция — создать на веб-странице таблицу размером 10x1000. Однако скорость их бега сильно различается.
/* Код теста 1 — затраченное время: 41 секунда*/
var table = document.createElement("ТАБЛИЦА");
document.body.appendChild(таблица);
for(var я = 0; я <1000; я++){
вар строка = table.insertRow(-1);
for(var j = 0; j < 10; j++){
вар ячейка = objRow.insertCell(-1);
cell.innerText = "( " + i + " , " + j + " )";
}
}
/* Код теста 2 — затраченное время: 7,6 секунды*/
var table = document.getElementById("ТАБЛИЦА");
document.body.appendChild(таблица);
var tbody = document.createElement("TBODY");
table.appendChild(tbody);
for(var я = 0; я <1000; я++){
var row = document.createElement("TR");
tbody.appendChild(строка);
for(var j = 0; j < 10; j++){
var cell = document.createElement("TD");
row.appendChild(ячейка);
cell.innerText = "( " + i + " , " + j + " )";
}
}
/* Код теста 3 — затраченное время: 1,26 секунды*/
var tbody = document.createElement("TBODY");
for(var я = 0; я <1000; я++){
var row = document.createElement("TR");
for(var j = 0; j < 10; j++){
var cell = document.createElement("TD");
cell.innerText = "( " + i + " , " + j + " )";
row.appendChild(ячейка);
}
tbody.appendChild(строка);
}
var table = document.getElementById("ТАБЛИЦА");
table.appendChild(tbody);
document.body.appendChild(таблица);
Разница между «Тестовым кодом 1» и «Тестовым кодом 2» здесь заключается в том, что при создании ячеек таблицы используются разные методы API. Разница между «Тестовым кодом 2» и «Тестовым кодом 3» заключается в несколько разном порядке обработки.
Мы не можем проанализировать такую большую разницу в производительности между «Тестовым кодом 1» и «Тестовым кодом 2». На данный момент известно, что InsertRow и InsertCell — это API-интерфейсы для конкретных таблиц в DHTML, а createElement и AppendChild — это собственные API-интерфейсы W3C DOM. Первое должно быть инкапсуляцией второго. Однако из этого мы не можем сделать вывод, что собственный API DOM всегда лучше, чем API, специфичный для объекта. Рекомендуется выполнить несколько базовых тестов его производительности, если вам нужно часто вызывать API.
Разница в производительности между «Тестовым кодом 2» и «Тестовым кодом 3» в основном связана с разницей в порядке их сборки. Подход «Тестового кода 2» заключается в том, чтобы сначала создать самый внешний объект <TABLE>, а затем последовательно создать в цикле <TR> и <TD>. Подход «Тестового кода 3» заключается в том, чтобы сначала построить всю таблицу в памяти изнутри наружу, а затем добавить ее на веб-страницу. Цель этого — максимально сократить количество раз, когда браузер пересчитывает макет страницы. Всякий раз, когда мы добавляем объект на веб-страницу, браузер пытается пересчитать расположение элементов управления на странице. Поэтому, если мы сможем сначала создать весь объект, который нужно сконструировать в памяти, а затем сразу добавить его на веб-страницу. Затем браузер выполнит только перерасчет макета. Если суммировать это в одном предложении, то чем позже вы выполните addChild, тем лучше. Иногда, чтобы повысить эффективность работы, мы можем даже рассмотреть возможность использования RemoveChild для удаления существующего элемента управления со страницы, а затем снова поместить его на страницу после завершения построения.
Увеличьте скорость накопления строк. При использовании AJAX для отправки информации мне часто может потребоваться собрать несколько относительно больших строк для завершения отправки POST через XmlHttp. Хотя предоставление такого большого объема информации может показаться неэлегантным, иногда нам приходится сталкиваться с такой необходимостью. Итак, насколько быстро происходит накопление строк в JavaScript? Давайте сначала проведем следующий эксперимент. Накопить строку длиной 30000.
/* Код теста 1 — затраченное время: 14,325 секунды*/
вар стр = "";
для (вар я = 0; я <50000; я++) {
стр += "ххххххх";
}
Этот код занял 14,325 секунды, и результаты были не идеальными. Теперь изменим код на следующий вид:
/* Код теста 2 — затраченное время: 0,359 секунды*/
вар стр = "";
для (вар я = 0; я <100; я++) {
вар суб = "";
for (var j = 0; j < 500; j++) {
суб += "ххххххх";
}
ул += суб;
}
Этот код занимает 0,359 секунды! Результат тот же: все, что мы делаем, это сначала собираем несколько строк меньшего размера, а затем собираем их в строки большего размера. Этот подход может эффективно уменьшить объем данных, копируемых в память на более поздних этапах сборки строк. Зная этот принцип, мы можем дополнительно разобрать приведенный выше код для тестирования. Код ниже занимает всего 0,140 секунды.
/* Код теста 3 — затраченное время: 0,140 секунды*/
вар стр = "";
for (var i1 = 0; i1 < 5; i1++) {
вар стр1 = "";
for (var i2 = 0; i2 < 10; i2++) {
вар стр2 = "";
for (var i3 = 0; i3 < 10; i3++) {
вар стр3 = "";
for (var i4 = 0; i4 < 10; i4++) {
вар стр4 = "";
for (var i5 = 0; i5 < 10; i5++) {
str4 += "ххххххх";
}
стр3 += стр4;
}
стр2 += стр3;
}
стр1 += стр2;
}
стр += стр1;
}
Однако описанный выше подход может быть не лучшим! Если информация, которую нам нужно отправить, находится в формате XML (на самом деле, в большинстве случаев мы можем попытаться собрать информацию, подлежащую отправке, в формате XML), мы также можем найти более эффективный и элегантный способ — использовать объекты DOM для сборки. символы для нас строка. В следующем абзаце сборка строки длиной 950015 занимает всего 0,890 секунды.
/* Использование объектов DOM для сбора информации — затраченное время: 0,890 секунды*/
вар xmlDoc;
если (тип браузера == BROWSER_IE) {
xmlDoc = новый ActiveXObject("Msxml.DOMDocument");
}
еще {
xmlDoc = document.createElement("DOM");
}
var root = xmlDoc.createElement("корень");
для (вар я = 0; я <50000; я++) {
var node = xmlDoc.createElement("данные");
если (тип браузера == BROWSER_IE) {
node.text = "xxxxxx";
}
еще {
node.innerText = "xxxxxx";
}
root.appendChild(узел);
}
xmlDoc.appendChild (корень вар ул)
;
если (тип браузера == BROWSER_IE) {
стр = xmlDoc.xml;
}
еще {
стр = xmlDoc.innerHTML;
}
Избегайте утечек памяти объектов DOM.
Утечки памяти объектов DOM в IE — проблема, которую разработчики часто игнорируют. Однако последствия это приносит очень серьезные! Это приведет к тому, что использование памяти IE продолжит расти, а общая скорость работы браузера значительно замедлится. Для некоторых веб-страниц, подвергшихся серьезной утечке, скорость работы будет удвоена, даже если их обновить несколько раз.
Наиболее распространенные модели утечки памяти включают «циклическую эталонную модель», «модель функции замыкания» и «модель порядка вставки DOM». Для первых двух моделей утечки мы можем избежать их путем разыменования при разрушении веб-страницы. Что касается «модели порядка размещения DOM», ее следует избегать, изменив некоторые общие привычки программирования.
Дополнительную информацию о модели утечки памяти можно быстро найти в Google, и эта статья не будет слишком подробной. Однако здесь я рекомендую вам небольшой инструмент, который можно использовать для поиска и анализа утечек памяти веб-страниц — Drip. Текущая новая версия — 0.5, а адрес загрузки — http://outofhanwell.com/ieleak/index.php.
Сегментированная загрузка и инициализация сложных страниц Для некоторых интерфейсов в системе, которые действительно сложны и неудобны в использовании IFrame, мы можем реализовать сегментированную загрузку. Например, для интерфейса многостраничных вкладок мы можем сначала загрузить и инициализировать страницу по умолчанию многостраничной вкладки, а затем использовать технологию AJAH (асинхронный JavaScript и HTML) для асинхронной загрузки содержимого на других страницах вкладок. Это гарантирует, что интерфейс может быть отображен пользователю в первую очередь. Распределите процесс загрузки всего сложного интерфейса в процесс работы пользователя.
Используйте GZIP для сжатия сетевого трафика.
В дополнение к упомянутым выше улучшениям на уровне кода мы также можем использовать GZIP для эффективного уменьшения сетевого трафика. В настоящее время все распространенные браузеры уже поддерживают алгоритм GZIP. Часто для поддержки GZIP нам нужно написать лишь небольшой объем кода. Например, в J2EE мы можем использовать следующий код в Filter, чтобы определить, поддерживает ли браузер клиента алгоритм GZIP, а затем использовать java.util.zip.GZIPOutputStream для реализации вывода GZIP по мере необходимости.
/* Код, определяющий, как браузер поддерживает GZIP*/
частная статическая строка getGZIPEncoding (запрос HttpServletRequest) {
String AcceptEncoding = request.getHeader("Accept-Encoding");
если (acceptEncoding == null) вернуть ноль;
AcceptEncoding = AcceptEncoding.toLowerCase();
if (acceptEncoding.indexOf("x-gzip") >= 0) вернуть "x-gzip";
if (acceptEncoding.indexOf("gzip") >= 0) вернуть "gzip";
вернуть ноль;
}
Вообще говоря, степень сжатия GZIP для HTML и JSP может достигать около 80%, а потеря производительности, которую он вызывает на сервере и клиенте, практически незначительна. В сочетании с другими факторами веб-сайты, поддерживающие GZIP, могут сэкономить нам 50% сетевого трафика. Таким образом, использование GZIP может значительно повысить производительность приложений, в которых сетевая среда не особенно хороша. Используя Fiddler, инструмент мониторинга HTTP, вы можете легко определить объем передаваемых данных на веб-странице до и после использования GZIP. Адрес загрузки Fiddler: http://www.fiddlertool.com /fiddler/.
Оптимизация производительности веб-приложений на самом деле очень большая тема. Из-за ограниченного объема эта статья может охватить лишь некоторые детали, а также не может полностью показать вам методы оптимизации этих деталей. Я надеюсь, что эта статья сможет привлечь внимание всех к веб-приложениям, особенно к оптимизации производительности на стороне клиента. В конце концов, методы серверного программирования всем известны уже много лет, и возможностей для использования производительности на стороне сервера не так уж и много. Улучшения методов на стороне клиента часто могут привести к неожиданному повышению производительности.