Алгоритмы и задачи клиентской оптимизации

Технологии будущего

Разбить на страницы
Показывать лекцию целиком

6.1. Профилируем JavaScript

Данный раздел написан после прочтения ряда заметок Джона Реси-га (автора JavaScript-библиотеки jQuery), в которых он рассказывал про особенности работы JavaScript в различных браузерах.

После существенной оптимизации CSS-селекторов и выхода SizzLe (http://sizzLejs.com/), который лишь немного уступает YASS (http://yass.webo.in/), автор jQuery сконцентрировал свои усилия на оптимизации работы с DOM-деревом и наиболее используемых методов и начал искать дополнительные способы для профилирования и оптимизации.

Также было написано дополнение для глубокого профилирования (http://ejohn.org/blog/deep-profiling-jquery-apps/) jQuery — оно помогло обнаружить методы, которые выполняются чересчур долго на реальных сайтах с jQuery. Дальше было проведено уточнение самих оптимизационных методов, которые, очевидно, являются не такими эффективными, как нам хотелось бы, — ведь непонятно, где именно и что конкретно нужно оптимизировать.

Для ответа на этот вопрос можно пойти по известному пути и замерить число вызовов функции из какого-либо метода. В этом нам может помочь Firebug, который позволяет увидеть эту информацию в соответствующей вкладке (еще и наряду со значениями времени выполнения каждого метода). К несчастью, очень неудобно вручную вбивать код, затем проверять результаты в консоли и определять, насколько они неудовлетворительны и изменились ли они с прошлого раза. Если бы только существовал способ получать эти числа в автоматическом режиме!

6.1.1. Профилирующие методы FireUnit

Джон Ресиг немного улучшил описанную ситуацию и добавил пару новых методов в FireUnit (http://fireunit.org/).

fireunit.getProfile();

Можно запустить этот код сразу после того, как вы использовали console.profile() ; и console.profileEnd() ; для перехвата проблемного участка кода, — и получить полный вывод профилирующей информации. Например, если мы запустим это, то получим из fireunit.getProfile() следующий объект JavaScript:

{
  "time": 8.443,
  "calls": 611,
  "data":[
{
  "name":"makeArray()",
  "calls":1,
  "percent":23.58,
  "ownTime":1.991,
  "time":1.991,
  "avgTime":1.991,
  "minTime":1.991,
  "maxTime":1.991,
  "fileName":"jquery.js (line 2059)"
  },
// etc.
]}
fireunit.profile( fn );
(рис 6.1) Профилирование вызовов JavaScript, источник: ejohn.org

Второй метод, добавленный в FireUnit, позволяет достаточно просто запускать и профилировать отдельные вызовы функций. Грубо говоря, этот метод запускает профайлер, исполняет функцию, останавливает профайлер, а затем возвращает результаты из getProfile(). Дополнительно он проверяет возможные исключения при вызове функции и гарантирует, что профайлер надежно выключен.

Это можно использовать примерно следующим образом:

fireunit.profile(function(){
  document.getElementsByClassName("foo");
});

6.1.2. Как это все применять

Во-первых, обязательно нужно установить последнюю версию (http://github.com/jeresig/fireunit ) FireUnit. Также можно скачать последнюю версию кода в виде расширения к Firefox: http://fireunit.org/fireunit-1.0a1.xpi

При запуске нужно будет убедиться, что:

  • обе вкладки Console и Script в Firebug включены;
  • свойство extensions.firebug.throttleMessages в about:config выставлено в false.
  • 6.1.3. Результаты

    Ниже приведены результаты вызовов из jQuery 1.3.2 ("методом" называется метод jQuery, который запускается с определенными параметрами, "вызовы" — это число вызовов функции, которые происходят во время работы метода, "O(n)" является грубой оценкой сложности вызова функции):

    Тестовая таблица
    Метод Вызовы O(n)
    .addClass("test"); 542 6n
    .addClass("test"); 592 6n
    .removeClass("test"); 754 8n
    .removeClass("test"); 610 6n
    .css("color", "red"); 495 5n
    .css({color: "red", border: "1px solid red"}); 887 9n
    .remove(); 23772 2n+n 2
    .append("

    test

    ");
    307 3 n
    .append("<p>test</p><p>test</p> <p>test</p><p>test</p><p>test</p>"); 319 3n
    .show(); 394 4n
    .hide(); 394 4n
    .html("<p>test</p>"); 28759 3n+n 2
    .empty(); 28452 3n+n 2
    .is("div"); 110
    .filter("div"); 216 2n
    .find("div"); 1564 16n

    Как можно видеть из этой таблицы по значениям O(n), большинство методов jQuery вызывают по крайней мере по одной функции на каждый элемент, который им нужно обработать. addClass запускает около шести функций на каждый элемент, filter — примерно две, а is — только одну.

    Также мы легко видим проблемные методы, напоминающие большие черные дыры, в которые утекает процессорное время — .remove(), .empty() и .html(). Все они имеют сложности с вызовом функций n 2 , что является значительной проблемой для производительности ваших сайтов. Все эти числа выросли по очень простой причине: .html() использует .empty(), .empty() использует .remove(), а .remove() работает крайне неэффективно. Если не начать профилировать вызовы функций на медленное выполнение (к слову сказать, большинство внутренних методов jQuery выполняются очень быстро), то и не удастся обнаружить код, написанный неэффективно.

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

    Метод Вызовы O(n)
    .remove(); 298 3n
    .html("<p>test</p>"); 507 5n
    .empty(); 200 2n

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

    Также весьма интересно интегрировать описанное профилирование как часть самого процесса разработки — чтобы сразу замечать очевидные недочеты в производительности.

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

    6.2. Проблемы при оценке производительности браузеров

    Данный раздел написан на основе статьи Christian Stockwell, отвечающего за производительность браузера IE. Измерение общей производительности веб-сайтов и браузеров крайне важно для большого числа людей: как для пользователей, использующих различные продукты, так и для разработчиков, оптимизирующих свои порталы. Также сами разработчики других браузеров внимательно следят за успехами их коллег по цеху и стараются ориентироваться на некоторые отраслевые стандарты качества.

    6.2.1. Измерение производительности браузера

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

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

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

    Часть проблем оценки производительности вызвана огромным количеством разнообразных действий, для которых используется браузер. Каждый день пользователи обращаются к широкому диапазону ресурсов — от насыщенного мультимедийным содержимым FLickr до спартанского GoogLe. Они могут столкнуться с интерактивным, насыщенным AJAX-скриптами сайтом, как Windows Live HotmaiL, или сайтом, содержащим лишь статический HTML, как, например, CraigsList, а некоторые из них станут использовать браузер для критически важных деловых приложений (например, построенных на его основе систем электронного документооборота).

    Производительность каждого из этих ресурсов часто зависит от производительности отдельной подсистемы браузера. Например, загрузка насыщенного изображениями сайта может зависеть от скорости, с какой браузер в состоянии загружать и распаковывать изображения. Напротив, производительность простенькой страницы зависит от того, как быстро браузер обрабатывает стандартный HTML. В следующем случае для хорошей производительности насыщенного AJAX-скриптами портала потребуется тесная интеграция JavaScript, CSS и DOM, — и это окажется в большей степени важным, нежели индивидуальная производительность каждого из названных компонентов. Когда на чашу весов кладутся FLash и SiLverLight, производительность будет зависеть от того, насколько хорошо встроены в браузер соответствующие подсистемы управления.

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

    6.2.2. Работа службы кэширования

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

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

    Что это означает в случае оценки производительности браузера? Например, при обращении к ресурсу http://www.microsoft.com ваш браузер может последовательно запрашивать данные из нескольких источников — с прокси-сервера вашей локальной сети, с сервера, расположенного к вам ближе всего, или с нескольких географически удаленных серверов.

    Для повышения скорости загрузки содержимого страниц и распределения нагрузки по сети эти серверы могут сохранять часть загружаемых вами данных у себя в памяти, чтобы остальные пользователи могли быстрее получать к ним доступ. Например, утром, придя на работу, вы первым делом просматриваете новости на http://www.msnbc.com. Скорее всего, браузер попытается сначала загрузить запрашиваемую страницу с прокси-сервера, затем с ближайшего к вам сервера корпоративной сети — перед тем как обратиться к прочим, удаленным от вас ресурсам. Как только страница загрузится, ваш рабочий прокси-сервер или сервер в локальной сети может "решить" (разумеется, в зависимости от предварительно сделанных настроек) сохранить часть ее содержимого. Когда другой пользователь, спустя десять минут, попробует обратиться по тому же адресу, его компьютер сначала получит порцию данных, уже сохраненных на прокси-сервере, вместо их повторной загрузки с удаленных серверов, что, в свою очередь, значительно уменьшит время загрузки страницы и приведет пользователя в прекрасное расположение духа.

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

    Принципы работы системы кэширования изложены здесь очень примитивно. Если требуется детальная информация по этому вопросу, то стоит обратиться к соответствующим ресурсам, включая, собственно, принципы работы НТТР-протокола.

    6.2.3. Размер образца

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

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

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

    Например, давайте посмотрим на таблицу пунктов, набранных двумя браузерами по итогам тестов навигации в пределах одной веб-страницы:

    (рис 6.2) Рис. 6.2. Проблема выбора подходящего среднего, источник: blogs.msdn.com

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

    6.2.4. Совместное использование канала

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

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

    6.2.5. Совместное использование ресурсов

    Совместное использование ресурсов приложениями на вашем компьютере также может повлиять на производительность браузера — по крайней мере так же сильно, как и совместное использование канала.

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

    Результаты тестирования двух браузеров одновременно, "бок о бок", могут оказаться совершенно некорректными. Например, платформа Windows имеет ограничение — возможны лишь 10 одновременных исходящих соединений; остальные запросы будут поставлены в очередь на выполнение по мере освобождения ресурсов и могут, в зависимости от необходимого временного интервала, завершиться успешно или с ошибкой. Такой способ тестирования означает, что вы, скорее всего, поставите один из браузеров в преимущественное положение тем, что запустите его на несколько микросекунд раньше соперника.

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

  • Закройте все прочие приложения, включая те, что скрыты в области уведомлений панели задач. Это особенно важно в случае, если какие-то из этих приложений используют сетевые ресурсы.
  • В командной строке запустите следующую команду для ограничения активности компьютера в процессе тестирования:
  • %windir%\\system32\rundll32.exe advapi32.dll,ProcessIdleTasks

    6.2.6. Взаимодействие с серверами

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

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

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

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

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

    6.2.7. Эффект наблюдателя

    Во многих областях сам факт наблюдения изменяет характер поведения наблюдаемых объектов. Этот феномен получил наименование "эффекта наблюдателя".

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

    6.2.8. "Холодный" старт против "горячего"

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

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

    Чтобы собрать наиболее непротиворечивые данные, откройте и закройте каждый браузер как минимум один раз перед тем, как начнете тестирование. Если все остальные приложения закрыты, это даст вашей операционной системе возможность загрузить нужные компоненты в память и обеспечит последовательность и точность результатов тестирования. Это также создаст равные условия конкуренции для разных браузеров, особенно в свете существования таких функций операционной системы, как Superfetch (http://www.microsoft.com/windows/windows-vista/features/superfetch.aspx), которая в ином случае обеспечит преимущества "любимому" браузеру.

    6.2.9. Содержимое веб-страниц

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

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

    Современные сайты обновляют содержимое очень часто. На Facebook или MySpace вы можете получить два совершенно разных результата между двумя щелчками кнопки мыши по одной и той же ссылке: за это время кто-то добавил запись или комментарий, загрузил картинку или еще каким-то образом изменил содержимое ресурса. На многих сайтах идет постоянная ротация рекламных баннеров, тем самым гарантируя, что любые два входа на сайт будут отличаться друг от друга.

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

    6.2.10. Дизайн страниц

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

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

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

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

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

    6.2.11. "Готово"

    Можете ли вы точно определить, что означает "веб-страница загружена"? Как быть в случае, если она содержит сложные AJAX-сценарии?

    Проблема при оценке производительности заключается в определении того, что, собственно, означает надпись "готово" в статусной строке браузера при загрузке страницы. А также в том, что некоторые страницы усложняются и разрастаются несогласованно друг с другом. Некоторые веб-программисты применяют маркер "загружено" (http://www.w3.org/ TR/htmL401/interact/scripts.htmL#h-18.2.3) как индикатор того, что браузер завершил разметку содержимого страницы для последующей загрузки. Этот маркер, к сожалению, интерпретируется разными браузерами по-разному.

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

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

    6.2.12. Надстройки браузера

    Использование надстроек означает, что с этого момента вы измеряете производительность не только самого браузера. Надстройки могут существенно влиять на производительность. Согласно данным, полученным из источников в Microsoft, IE используется в совокупности с дюжинами надстроек (относительно Mozilla ситуация абсолютно идентичная).

    Любая из этих надстроек может проявлять произвольную активность внутри браузера. Иллюстрацией воздействия может служить следующая ситуация: пользователи, отстаивающие свои предпочтения в отношении определенного браузера, внезапно обнаруживают, что любая альтернативная программа работает быстрее лишь потому, что их любимый браузер перегружен надстройками и дополнениями, а альтернативный представляет собой чистую, без всякого "мусора" программу. Например, пользователь обремененного несколькими дополнениями Firefox может сменить его на IE, увидев, что тот работает быстрее, а в это время пользователь IE переходит на Firefox по той же схеме исходя из тех же причин. Здесь нет никакого противоречия — такие примеры лишь демонстрируют решающее влияние надстроек.

    Для блокировки надстроек в IE 8 необходимо вызвать пункт Manage add-ons из меню Tools. В появившемся диалоговом окне выберите All Addons и последовательно заблокируйте все надстройки из списка. Если вы дружите с командной строкой, можете выполнить команду iexplore.exe -extoff для запуска IE без дополнений.

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

    6.3. Перспективы "быстрого" JavaScript

    В этом разделе рассматривается часть прикладных методов, положенных в основу разработки YASS (http://yass.webo.in/) — самой быстрой библиотеки для выбора элементов по CSS-селекторам.

    6.3.1. Условное ветвление

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

    var a = 1,
      b = 2,
      c = 3;
    if(a == 1) {
      if (b == 2) {
        if (c == 3) {
          ...
        }
      }
    }

    Это, заметим с интересом, работает так же быстро, как и совмещенный if:

    if(a == 1  b == 2  c == 3){
      ...
    }

    Однако последний вариант немного меньше по размеру. Если не стоит задача минимального размера кода, то для улучшения читаемости стоит использовать первый вариант. Если же мы минимизируем все, то можно рассмотреть возможность использования if-then-else выражения. Но нужно иметь в виду, что производительность таких конструкций:

    var v = a == 1 ? b == 2 ? c == 3 ? 1 : 0 : 0 : 0;

    примерно на 10-50% меньше, чем у обычного ветвления, рассмотренного чуть выше.

    В том случае, когда все переменные у нас числовые, проверка равенства их суммы заданной будет выполняться на 5—10% быстрее:

    if (a + b + c == 6) {
      ...
    }

    Если же нам нужно проверить просто существование переменных и их неотрицательность (т. е. то, что переменные не undefined, не NaN, не null, не ‘’ и не 0), то следующий вариант будет работать еще на 5—10% быстрее, чем предыдущий случай (и на 10—20% быстрее, чем самый первый пример):

    if (a  b  c) {
      ...
    }

    Очень часто нам нужно проверить что-то более сложное, чем просто число. Например, совпадение строки с заданной или равенство объектов. В этом случае нам просто необходимо следующее сравнение:

    var a = 1,
      b = 2,
      c = '3';
    if (a == 1  b == 2  c === '3') {
      ...
    }

    Здесь мы используем сравнение без приведения типов ===, которое в случае нечисловых переменных работает быстрее обычного сравнения на 10—20%.

    6.3.2. Выбор в зависимости от строки

    Достаточно часто нам нужно выбрать одну из условных ветвей, основываясь на заданной строке. Обычно для этого используются либо методы объекта RegExp (exec, test), либо строковые методы ( match, search, indexOf ). Если нам нужно просто проверить соответствие строки какому-то регулярному выражению, то лучше всего для этого подойдет именно test:

    var str = 'abc',
      regexp = new RegExp('abc');
    if (regexp.test(str)) {
      ...
    }

    Такая конструкция отработает на 40% быстрее, чем аналогичный exec:

    if (regexp.exec(str)[1]) {
      ...
    }

    Строковый метод match аналогичен методу exec у создаваемого объекта RegExp, но работает на 10—15% быстрее в случае простых выражений. Однако метод search работает чуть медленнее (5—10%), чем test, потому что последний не возвращает найденную подстроку.

    В том случае, если регулярное выражение требуется "на один раз", подойдет более быстрая (примерно на 10% относительно варианта с инициализацией нового объекта) запись:

    if (/abc/.test(str)) {
      ...
    }

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

    if (str.indexOf('abc') != 1) {
      ...
    }

    6.3.3. Точное соответствие и хэши

    Давайте теперь рассмотрим следующий вариант регулярного выражения: /alblc/.В этом случае нам нужно проверить в заданной строке наличие одного из возможных вариантов (или равенство строки этому варианту). В случае точного соответствия быстрее регулярного выражения (на 50%) будет проверка строки как ключа какого-либо хэша:

    var hash = {'a':1, 'b':1},
    str = 'a';
    if (h[str]) {
      ...
    }

    Быстрее (на 20%) такого хэша будет только точная проверка строки на определенные значения:

    if (ss === 'a' || ss === 'b'){
      ...
    }

    Если рассмотреть 3 конструкции: вложенный if, switch с соответствующим значением и проверка значений в хэше, — то стоит отметить следующую интересную особенность. При небольшом уровне вложенности if (если всего значений немного или мы очень часто выходим по первому-второму значению), конструкции if и switch обгоняют по производительности хэш примерно на 10%. Если же у нас значений много и они все примерно равновероятны, то хэш отрабатывает в общем случае быстрее уже на 20%. Это в равной степени относится как к установлению значений переменных, так и к вызову функций. Поэтому для создания ветвления с вызовом функций лучше всего использовать именно хэш.

    При анализе CSS-селектора можно выделить несколько подзадач, описываемых как "проблема выбора".

  • Ветвление для простого случая выполнено при помощи проверки входной строки через test:
    if (/^[\w[:#.][\w\]*^|=!]*$/.test(selector)) {
      ...
    } else {
      ...
    }
  • Ветвление для простейшего случая (когда нам нужно выбрать по идентификатору или по классу). Поскольку всего значений у нас 5 и 3 из них относительно равновероятны (выбор по классу, по идентификатору или по тегу), используется switch:
    switch (firstLetter) {
      case '#':
    ...
      break;
      case '.':
      ...
      break;
    case ':'
      ...
      break;
    case '[':
      ...
      break;
    default:
      ...
      break;
    }
  • Абсолютно аналогичную картину мы наблюдаем для выбора правильного отношения "родитель-ребенок" (>, +, $$\sim$$, ): тут тоже только switch:
    switch (ancestor) {
      case ' ':
      ...
      break;
    case '∼':
      ...
      break;
    case '+':
      ...
      break;
    case '>':
      ...
      break;
    }
  • Наконец, выбор соответствующей проверочной функции для child-модификаторов ( first-child, last-child, nth-child, и т. д.) и выбор проверочной функции для атрибутов ( $$\sim$$ =, *=, = и т. д.) осуществляется уже через специальные хэши:
    _.attr = {'': ... , '=': ... , '=': ... , '^=': ... ...}
  • 6.3.4. Итоговая таблица

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

    Задача Средство решения
    Проверка числового значения Обычное сравнение (==)
    Проверка нескольких числовых значений Сравнение их суммы
    Проверка, что число не нуль, или проверка на существование Проверка отрицания к заданной переменной (!)
    Разбор строки и выделение частей в массив String.match(RegExp) или RegExp.exec(String)
    Проверка строки на соответствие регулярному выражению RegExp.test(String)
    Проверка строки на точное соответствие (либо соответствие одному из набора значений) if без приведения типов (===)
    Выбор в зависимости от точного значения (значений 1—2) Условная конструкция if
    Выбор в зависимости от точного значения (значений 3—8) switch
    Выбор в зависимости от точного значения (значений больше 8) Хэш с ключами, соответствующими значениям

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

    ( http://webo.in/articles/habrahabr/78-javascript-constructionsperformance/ ) и сделать соответствующие выводы.

    6.4. Реализация логики CSS3-селекторов

    На данный момент насчитывается несколько десятков различных JavaScript-библиотек, которые предлагают механизм выбора элементов по CSS-селектору. В чем же разница между ними всеми? Разница в скорости нахождения элементов. Иногда она может варьироваться довольно сильно.

    Но в последнее время появилось несколько явных фаворитов на этом поприще. Речь идет про Sizzle (движок выборки элементов, автором которого является Джон Ресиг и который включен в jQuery 1.3+), Peppy (достаточно хорошо стартовавший и обогнавший на первых порах Sizzle, но потом заброшенный автором) и некоторые другие, в том числе и YASS (http://yass.webo.in/).

    Поэтому возникает резонный вопрос, почему нельзя сделать быстрое мини-ядро для CSS-селекторов, которое обеспечит базовую функциональность для работы с DOM (например, совсем базовую — просто выборку элементов)? И, самое главное, чтобы это работало не медленнее (в идеале даже быстрее), чем вызовы в самом браузере.

    6.4.1. Основы быстродействия

    Но описанная задача имеет решение. Можно создать библиотеку, которая будет осуществлять базовые операции практически так же быстро, как и сам браузер (а в некоторых случаях даже быстрее — за счет кэширования). И это удалось сделать в достаточно сжатые сроки. Далее речь пойдет о самой быстрой (на момент написания книги) библиотеки для выбора элементов по CSS-селекторам. Каковы же были причины для написания такой библиотеки?

    Во-первых, такой код должен и кэшировать выборки (DOM-вызовы дорого обходятся, все нормальные JavaScript-программисты уже их кэши-руют — так упростим им задачу и повысим быстродействие).

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

    В-третьих, библиотека должна возвращать сами элементы, чтобы к ним можно было "прикрутить" любые обертки и нарастить методы. Все обертки ресурсоемки. Если нужно просто 200 раз поменять HTML на странице у заданных элементов, то они не нужны. Достаточно и проверок со стороны браузера на допустимость выполняемых операций.

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

    6.4.2. Примеры вызовов

    Синтаксис такой библиотеки до безобразия прост:

  • _(‘p’) — вернет все параграфы на странице;
  • _(‘p a’) — или все ссылки в них;
  • _(‘p a.blog’) — или все ссылки с классом blog.
  • 6.4.3. Еще один велосипед?

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

    Или же данный код можно будет доработать и включать в основу высокопроизводительных библиотек, которые уже будут при его помощи ре-ализовывать свои методы (одной из таких библиотек, с которыми YASS уже интегрирован, является js-core, http://code.googLe.eom/p/js-core/). Также возможна замена кодом YASS встроенного механизма выборки элементов по CSS-селектору в таких распространенных библиотеках, как MooTooLs, Prototype, jQuery, YUI для повышения их быстродействия.

    Но давайте рассмотрим процесс выборки CSS-селекторов более детально.

    6.4.5. Выборка CSS-селекторов

    Начнем с самого простого: чего мы хотим добиться? Мы хотим, задав произвольную строку CSS-селектора, соответствующую спецификации (http://www.w3.org/TR/2005/WD-css3-seLectors-20051215/), получить на выходе массив из всех элементов, соответствующих этой самой строке. Вроде пока все просто.

    В качестве иллюстрации спецификации можно привести следующие примеры (работает во всех современных браузерах и IE8+):

    // вернет элемент с идентификатором my_id
    querySelectorAll('#my_id')
    // вернет все элементы с классом external
    querySelectorAll('.external')
    // вернет все абзацы на странице
    querySelectorAll('p')

    Однако уже тут можно отметить один момент: очень часто нам нужно выбрать просто элемент по его идентификатору или найти все элементы с определенным классом. Эти операции встречаются достаточно часто во всех JavaScript-библиотеках, поэтому они должны выполняться максимально быстро. Запускать весь механизм анализа входной строки селектора просто в том случае, когда нам нужно вернуть один-единственный элемент, заданный с помощью идентификатора, крайне неосмотрительно. Здесь мы можем воспользоваться принципом ленивого программирования: "не делай того, чего можно не делать", — и достаточно сильно ускорить работу для простейших случаев.

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

    Может так случиться, что регулярное выражение обеспечивает корректность, и замена его никак не приведет к потере читаемости кода и непонятному выигрышу (или даже проигрышу) в скорости выполнения. Тогда можно заменить, например, exec на связку test и charAt / substr: это позволит увеличить производительность примерно на 20%. Если данный участок кода выполняется в цикле многократно, ускорение может оказаться достаточно существенным.

    В YASS данная задача решена следующим образом:

    // проверяем, удовлетворяет ли селектор простому случаю
    if (/^[\w[:#.][\w\]*^|=!]*$/.test(selector)) {
    // в случае положительного результата инициализируем переменную,
    // которая отвечает за '#', '.' , ':' или '[' в начале селектора
      var firstLetter = selector.charAt(0);
      ...
    }

    6.4.6. От простого к сложному

    Но давайте рассмотрим, как решена общая задача по разбору CSS-селекторов. Если принять во внимание, что селектор может быть задан в виде p a.link, form input[type=radio], то логику его разбора можно схематично записать в следующем виде:?

  • Выбираем последовательности селекторов, которые находятся между запятыми. Далее работаем с каждой последовательностью в отдельности. На выходе все последовательности объединяем в итоговый массив ( sets ).
  • В последовательности селекторов у нас есть набор элементарных селекторов, которые "вложены" друг в друга (для нашего примера это p a.link ). Нам нужно разбить последовательность на части и разобрать каждую такую часть, учитывая, что родительскими элементами для следующей части будут выбранные элементы из предыдущей. За "превращение" дочерних узлов в родительские (прямо процесс взросления получается) отвечает массив nodes.
  • Каждый элементарный элемент уже придется разбирать с помощью регулярного выражения, чтобы вычленить части, отвечающие за идентификатор, класс и модификаторы CSS 2/3. Разбирать быстрее всего при помощи exec, а потом записывать в переменные части полученного массива:
    single = regexp.exec(single);
    tag = single[1];
    id = single[2];
    ...
  • И наконец, третий цикл проходится по всем родительским элементам и пытается выбрать из них дочерние узлы, соответствующие заданным в CSS-селекторе параметрам.
  • Как мы видим, основная логика данной задачи включает как мини- мум одно регулярное выражение (использование indexOf и substring будет при такой сложности намного более ресурсоемко) и 3 цикла (которые нужно сделать максимально быстрыми). Не стоит перечислять все возможности быстрого выбора элементов, просто сделаем акцент на некотоых аспектах.

    6.4.7. Перебор массива

    Пусть у нас объявлен некоторый массив a, с элементами которого мы совершаем какие-либо действия. Нам нужно перебрать все элементы строго по возрастанию (порядок важен), т. е. просто while(i-) мы использовать не можем. Наиболее распространенным сейчас способом будет обычный for:

    for (var j=0, item = a[j]; item; item = a[j++]) { 
      item++;
    }
      Естественно, он на 30-40% медленнее следующего while: 
      var j = 0,
      item,
    len = a.length; 
    while (j < len) 
    { item = a[j++]; 
    item++;
    }

    Однако если нам нужно выполнить какие-либо действия с элементом массива, то без кэширования его в локальную переменную никак не обойтись. В этом случае следующий вариант с while (через проверку существования элементов при инкременте) будет еще быстрее — на 5—10%:

    var j = 0,
      item;
    while (item = a[j++]) {
      item++;
    }

    Очевидно, что для всех трех циклов в YASS (http://yass.webo.in/) применяется именно он.

    Если же нам абсолютно не важен порядок элементов (например, просто нужно найти нужный или вернуть false), то логично будет воспользоваться обратным while:

    while (idx—) {
      sets[idx].yeasss = null;
    }

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

    6.4.8. Уникальность элементов

    Одной из основных проблем в ходе выбора элементов по CSS-селек-тору является корректность конечного набора. В случае div p у нас могут оказаться вложенные div, и если мы просто переберем все родительские элементы и объединим получившиеся дочерние, то конечный набор будет содержать дубли. Чтобы избежать такого рода ошибок, нам нужно как-то отмечать элементы, которые мы выбрали.

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

    Схематично представить работу данного флага можно следующим образом:

    for (child in children) {
      if (!children[child].yeasss) { if (last) 
      {children[child].yeasss = 1;
      }
      newNodes = children[child];
       }
    }

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

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

    Сброс флага (о котором шла речь в предыдущем разделе) осуществляется уже для окончательного массива элементов (после завершения всех циклов) и необходим как для корректности дальнейших выборок, так и для предотвращения утечек памяти в IE (расширенные свойства элементов вызывают микро-утечки в IE 6/7).

    6.4.9. Подводя черту

    YASS (http://yass.webo.in/) создавалась и продолжает разрабатываться для поиска и реализации наиболее производительных методов для решения определенного круга задач. Ее можно применять как в учебных целях, так и в чисто практических (например, для составления набора неиспользуемых на сайте CSS-селекторов — с помощью YASS это реализуется быстрее всего).

    6.5. API для CSS-селекторов в браузерах

    Данный раздел написан под впечатлением от статьи DOM selectors API in Firefox 3.5 (http://hacks.mozilla.org/2009/06/dom-selectors-api/) Джон Ресиг (автора jQuery и евангелиста веб-стандартов в Mozilla), в которой освещается текущая поддержка браузерами стандартов в этом направлении и некоторые вопросы производительности.

    Предварительная версия документа API для селекторов (http://dev.w3.org/2006/webapi/selectors-api/), опубликованная консорциумом W3C, представляет собой относительно новый взгляд для JavaScript-разработчиков на то, как можно выбирать DOM-элементы на страницы при помощи CSS-селекторов. В одном этом документе собраны все тонкости такого сложного процесса, как поиск, выборка элементов из DOM-дерева и представление результата, доступного по упорядоченному интерфейсу.

    Несмотря на все недавние войны по поводу интеграции стандартов в браузеры, этот является одним из наиболее поддерживаемых: его можно использовать прямо сегодня в браузерах Internet ExpLorer 8, Chrome и Safari, а также в Firefox 3.5 и Opera 10.

    6.5.1. Используем querySelectorAll

    API для селекторов предоставляет два метода для всех DOM-доку-ментов, элементов и фрагментов (удивительно удобно, хотя два — это, может быть, немного перебор, но иначе получение уникальных элементов на странице каждый раз выливалось бы в дополнительный JavaScript-код): querySelector и querySelectorAll. Оба метода практически идентичны: оба принимают CSS-селектор и возвращают DOM-элементы (за исключением того, что querySelector возвращает только первый найденный элемент).

    Например, давайте рассмотрим следующий участок HTML-кода:

    <div id="id" class="class">
    <p>Первый абзац.</p>
    <p>Второй абзац.</p>
    </div>

    Мы можем использовать querySelectorAll, чтобы сделать красным фон всех параграфов внутри div с идентификатором id.

    var p = document.querySelectorAll("#id p");
    for ( var i = 0; i < p.length; i++ ) {
    p[i].style.backgroundColor = "red";
    }

    А также мы можем найти самый первый параграф этого div, который является его прямым потомком и у которого задан класс class. Ему мы присвоим класс first.

    document.querySelector("div.class > p:first-child")
    .className = "first";

    В повседневной жизни описанные процедуры могут быть весьма запутанными в связи с большим объемом JavaScript-/DOM-кода, приводя к многострочным записям и множеству выборок для достижения какой-либо цели. Сразу стоит отметить, что хотя производительность CSS-селекторов уже интегрирована в браузеры, но ее быстродействие (особенно для ряда сложных случаев CSS3-спецификации) может быть весьма низкой.

    Для преодоления этой проблемы необходимо использовать кэширующие техники, которые реализованы, например, в YASS (http://yass.webo.in/).

    Хотя внешне применение методов API для селекторов весьма просто (каждый принимает только один аргумент на вход), проблемы наступают при выборе подходящей спецификации CSS-селекторов. API для селекторов привязано (и это на самом деле очень хорошо: представьте ситуацию, что браузер в CSS-коде понимал бы один набор селекторов, а при использовании JavaScript предоставлял бы уже совершенно другой доступный набор) к естественному движку CSS-селекторов в браузере, который нужен для применения стилей для конкретных элементов. Для большинства браузеров (Firefox, Safari, Chrome и Opera) это означает, что у вас есть доступ к полной гамме CSS3-селекторов. В то же время Internet Explorer 8 обеспечивает более ограниченный функционал и поддерживает только CSS2-селекторы (работать с которыми до сих можно только с трудом в силу отсутствия их полноценной поддержки в IE 6/7).

    Итак, самой большой проблемой для новых пользователей API для селекторов является выбор корректной CSS-спецификации для использования. Особенно это актуально для большинства разработчиков, который пишут кроссбраузерный код и поэтому ориентируются на CSS1-набор, работающий во всех браузерах.

    Изучение спецификаций CSS2- (http://www.w3.org/TR/CSS2/selector.html) и CSS3-селекторов (http://www.w3.org/TR/css3-selectors/) будет отличным шагом в увеличении своего багажа знаний.

    6.5.2. Суровая реальность

    Наиболее часто встречающийся случай применения API для CSS-се-лекторов — это использование его не напрямую, а при помощи разнообразных сторонних библиотек, которые также обеспечивают функциональность CSS-селекторов для DOM. Сегодня основная проблема внедрения применения API для селекторов заключается в том, что они не поддерживаются во всех браузерах, для которых ведется разработка (в частности, это IE 6, IE 7 и Firefox 3). Поэтому пока эти браузеры еще не вышли из обращения, нам будут требоваться некоторые промежуточные утилиты для восстановления недостающей функциональности CSS-селекторов для DOM.

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

    Некоторые из существующих фреймворков, использующих по возможности API для селекторов:

  • jQuery (http://jquery.com/)
  • Prototype (Prototype (http://prototypejs.org/))
  • Dojo (Dojo (http://dojotooLkit.org/))
  • MooTooLs (http://mootooLs.net/)
  • Стоит также подчеркнуть, что применение нового API влечет значительный выигрыш в производительности (по сравнению с обычными методами выбора элементов из DOM при помощи JavaScript). Вы сможете самостоятельно убедиться в этом, просто судя по улучшению ситуации в JavaScript-библиотеках, которые начали внедрять новое API для селекторов.

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

    (рис 6.3) Прирост в производительности после внедрения API для селекторов, источник: hacks.mozilla.org

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

    6.5.3. Тестовый набор

    Чтобы сравнить определение спецификации API для селекторов (http://dev.w3.org/2006/webapi/seLectors-api/) с фактической реализацией, было создано специальное тестовое окружение (автор — Джон Ресиг из MoziLLa). Это тестовое окружение может быть в том числе использовано для проверки основных браузеров на уровень соответствия стандартам.

    Текущие результаты для браузеров, которые поддерживают это API, следующие:

  • Firefox 3.5: 99,3%
  • Safari 4: 99,3%
  • Chrome 2: 99,3%
  • Opera 10b1: 97,5%
  • Internet Explorer 8: 47,4%
  • Internet Explorer 8, как уже было упомянуто ранее, не реализует логику CSS3-селекторов (наверное, в силу того, что спецификация еще не утверждена w3.org), поэтому проваливает большую часть тестов.

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

    6.6. Canvas: один шаг назад, два шага вперед

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

    6.6.1. Предыстория

    В 1998 году компания Microsoft при поддержке других крупных производителей предложила W3C свой стандарт отображения векторной информации — VML (англ. Vector Markup Language — векторный язык разметки). Он был основан на текущем формате представления документов в вебе — HTML — и расширял его до некоторого "векторного" языка. Компания Microsoft активно продвигала и продвигает данный формат и включает его во все версии IE начиная с 5.0. В рамках стандарта он окончательно закреплен в качестве части спецификации Open Office XML (ISO 29500:2008 и ECMA-376) в 2008 году.

    В этом же 1998 году Adobe, IBM, Netscape и Sun вносят в W3C предложение о рассмотрении своего стандарта в противовес Microsoft — PGML (англ. Precision Graphics Markup Language — точный графический язык разметки). Не желая делать ни один стандарт проприетарным, W3C создает рабочую группу, которая на основе имеющихся предложений в 1999 году создает набросок еще одного стандарта — SVG (англ. Scalable Vector Graphics — масштабируемая векторная графика). Этот стандарт (хотя до сих пор закрепленный только в форме рекомендации, последняя версия 1.1 от 2003 года) находит гораздо большую поддержку у производителей браузеров и на данный момент включен практически везде (кроме, естественно, IE).

    Сейчас большинство JavaScript-библиотек, которые предлагают работу с векторной графикой, включают поддержку SVG для всех браузеров и поддержку VML для IE. В качестве характерного примера можно привести Яндекс.Карты или Google Maps. Оба формата (SVG и VML) обладают практически одинаковыми возможностями; например, ниже приведен код на VML для отображения синего овала:

    <html xmlns:v="VML">
      <style type="text/css">
        v\:*{behavior:url(#default#VML);position:absolute}
      </style>
      <body>
        <v:oval style="left:0;top:0;width:100;height:50"
        fillcolor="blue" stroked="f"/>
      </body>
    </html>

    Этот же код на SVG:

    <?xml version="1.0"?>
            <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
    "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
            <svg xmlns="http://www.w3.org/2000/svg" width="100" height="50">
              <ellipse cx="50" cy="25" rx="50" ry="25" fill="blue"
              stroke="none" />
            </svg>

    6.6.2. Появление Canvas

    Спецификация Canvas (как отдельной области на странице, внутри которой можно отображать графические объекты) в 2005 году изначально была предложена со стороны Apple для поддержки некоторых приложений внутри движка WebKit (на данный момент его используют браузеры Safari и Chrome). Рабочая группа W3C включила Canvas в Web Applications 1.0, который вошел в готовящийся стандарт HTML 5.0.

    Сейчас встроенная поддержка Canvas реализована в том или ином виде во всех современных браузерах. В IE версии 8 и ниже она эмулируется при помощи отдельного VML-документа.

    В качестве основного отличия от VML и SVG стоит назвать принципиальную невозможность описать какой-либо элемент в Canvas, используя строго HTML и CSS: все действия производятся только при помощи JavaScript. Также стоит упомянуть, что SVG оперирует с отдельными векторными объектами и их взаимосвязями, тогда как Canvas предлагает интерфейс к пиксельной области на странице, не предлагая устанавливать дополнительных связей (все они находятся в зоне ответственности JavaScript).

    6.6.3. Основные возможности

    Canvas предлагает достаточно мощное API для действий над двумерными графическими объектами. В том числе, это вывод и преобразование текста (в том числе загрузка произвольного шрифта), отображение двумерных объектов (прямоугольников, треугольников, кругов и многоугольников), работа с векторной графикой (задается как объект пути), преобразование над фигурами (прозрачность, перемещение, поворот и даже матрица преобразования), работа с отдельными линиями и тенями. Также возможна работа с изображениями в попиксельном формате (что позволяет сделать практически полноценный Photoshop внутри отдельного браузера) и коррекция цвета.

    Давайте рассмотрим следующий простой пример использования Canvas:

    <html>
      <head>
        <script type="application/x-javascript">
    function draw() {
      var canvas = document.getElementById("canvas");
      var ctx = canvas.getContext("2d");        
      
      ctx.fillStyle = "rgb(200,0,0)";
    ctx.fillRect (10, 10, 55, 50);
    ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
    ctx.fillRect (30, 30, 55, 50);
    }
    window.onload = draw;
    </script>
    </head>
    <body>
    <canvas id="canvas" width="300" height="300"></canvas>
    </body>
    </html>

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

    (рис 6.4) Пример простого изображения при помощи Canvas,источник: developer.mozilla.org

    Дополнительно Canvas позволяет определить практически любые действия пользователя над описываемыми объектами (перемещение и нажатия мыши) и загрузить в область объекты MathML и SVG. Как мы видим, на данный момент это полноценная платформа для произвольной анимации прямо в том же браузере, который предназначен для просмотра отдельных HTML-страниц. Можно с уверенностью предсказать, что через пару лет обычные FLash-банеры будут вытеснены их более интерактивными и более "поддерживаемыми" коллегами на основе Canvas (и, скажем, VML для семейства браузеров IE).

    Подробнее со спецификацией Canvas можно ознакомиться, например, на странице WHATWG (http://www.whatwg.org/specs/web-apps/ current-work/muLtipage/the-canvas-eLement.htmL).

    6.6.4. Примеры использования Canvas, SVG и VML

    Примеров использования Canvas довольно много, и они частично охватывают уже известные области 2D-графики.

    2D-проекции 3D-объектов

    С помощью Canvas можно изображать 2D-проекции трехмерных объектов. В силу того, что все преобразования выполняются на пиксельном уровне, достаточно просто создать необходимые интерфейсы для изометрических проекций трехмерных объектов. Видимо, уже скоро на основе Canvas появятся полноценные 3D-экскурсии в браузерах или онлайн-игры "от первого лица".

    (рис 6.5) Изображение чайника при помощи Canvas, источник: www.nihilogic.dk

    Typeface.js

    В качестве следующего применения стоит упомянуть библиотеку typeface.js (http://typeface.neocracy.org/) и созданный на ее основе метод отображения произвольных шрифтов на сайте (ранее для этой цели активно использовался Flash). Единственным минусом данного подхода (на фоне повышенного быстродействия в связи с естественной поддержкой Canvas и простоты использования) является большой размер файла самих шрифтов.

    Для применения библиотеки typeface.js необходимо перевести требуемый шрифт в некоторый объект, описываемый с помощью соответствующих конструкций на JavaScript, затем этот объект будет использоваться для представления произвольного текста на сайте (если быть точным, то в качестве объекта выступают отдельные символы шрифта). Размер файла шрифта в текстовом формате сильно варьируется от количества включенных в него символов и может составлять от 50 до 500 Кб (в неархивированном виде).

    Для больших порталов (где размер страницы составляет 500-1000 Кб) данный подход вполне приемлем (если "стилизованных" заголовков будет не так много, чтобы лишний раз не нагружать браузер преобразованиями страницы). Для небольших сайтов загрузка нескольких десятков Кб JavaScript-кода для стилизации 10 Кб HTML- и CSS-кода выглядит не очень уместной.

    (рис 6.6) Применение Canvas для стилизации шрифтов, источник typeface.neocracy.org

    Cufo’n

    Cufo’n в качестве основы использует для отображения произвольных шрифтов уже SVG. Однако проблемы с конвертацией файлов шрифтов в промежуточный формат присутствуют и здесь.

    Более подробно ознакомиться и загрузить необходимые файлы можно по адресу http://cufon.shoqolate.com/generate/.

    Prosessing.js

    Processing — язык программирования, созданный Casey Reas и Benjamin Fry в академических целях и направленный на кроссплатформенную обработку двумерных графических объектов. Он может быть реализован на любой аппаратной платформе путем преобразования исходных конструкций в платформозависимые инструкции.

    Хорошим примером использования Canvas является реализация Processing для JavaScript (автор реализации — Джон Ресиг) — библио- тека Processing.js (processingjs.org/). Она предполагает полную совместимость инструкций данного языка с преобразованиями объекта Canvas. На данный момент доступно несколько проектов, применяющих Processing.js, в частности, несколько игр в браузерах, например, "Защи- та башнями".

    (рис 6.7) Использование processing.js для игры "Защита башнями", источник: willarson.com

    Raphaеl

    Если предыдущий пример был посвящен использованию Canvas больше в развлекательных целях, то библиотека Raphael.js (http://raphaeljs.com/) преследует сугубо практические цели (хотя и делает это с помощью SVG + VML). С ее помощью можно удобно и красиво представлять различные объемы данных во всем привычном формате графиков.

    Применение этой библиотеки предельно просто: обычно нужно объявить необходимые данные и задать один из множества доступных представлений (или создать свое собственное). Более подробно с данной библиотекой можно ознакомиться на ее официальном сайте —http://raphaeljs.com/.

    6.6.5. Проблемы быстродействияя

    На данный момент Canvas при решении большинства задач справляется быстрее, чем SVG. Достаточно давно был разработан пример использования Canvas для ряда задач GoogLe Maps (http://www.ernestdeLgado.com/gmaps/canvas/ddemo1.htmL). В нем зафиксирован прирост скорости в 200500% (для всех браузеров, которые поддерживают Canvas).

    (рис 6.9) Пример использования Raphael.js для отображения данных, источник: raphaeljs.com(рис 6.8) Быстродействие Canvas, источник: www.ernestdelgado.com(рис 6.11) Возможности и быстродействие Canvas, источник: prototype-graphic.xilinus.com(рис 6.10) Сравнение быстродействия Canvas и SVG, источник: intertwingly.net

    В результате другого тестирования (http://prototype-graphic.xilinus.com/samples/shape.html ) Canvas также демонстрирует значительное преимущество перед SVG, но менее широкий набор возможностей.

    В качестве примера можно привести еще один тест (http://intertwingly.net/ stories/2006/07/10/penroseTiling.html) скорости отображения объектов в Canvas и SVG. Здесь SVG снова проигрывает (но совсем незначительно, в большинстве браузеров разницы почти нет).

    Для уточнения вопросов производительности можно обратиться к исследованию (http://www.borismus.com/canvas-vs-svg-performance/), установившему закономерность между производительностью SVG, Canvas и параметрами изображения. В результате оказывается вполне очевидным, что при увеличении числа объектов (для SVG — векторных) производительность SVG падает сильно (почти экспоненциально), а Canvas остается на стабильном уровне. Здесь стоит отметить, что размер активной области при этом не изменяется.

    (рис 6.12) Производительность Canvas и SVG при увеличении числа объектов, источник: www.borismus.com

    Однако если мы начнем увеличивать область построения (размеры объектов), то тут векторный формат показывает себя во всей красе: производительность практически не меняется. Производительность Canvas падает (как и следовало ожидать) квадратичным образом от числа объектов (площадь активной области увеличивается квадратично).

    (рис 6.13) Производительность Canvas и SVG при увеличении размера объектов, источник: www.borismus.com

    Из этого можно сделать простой вывод: если вы собираетесь использовать точечную (пиксельную) графику, то лучше Canvas для этой цели ничего не подходит. При работе с большими (по площади) векторными объектами лучше применять SVG. Также будет необходимо дублировать всю функциональность через VML для IE 8 и ниже.

    6.7. Вычисляем при помощи Web Workers

    Данный раздел написан под впечатлением статьи Джона Ресига "Computing with JavaScript Web Workers" (http://ejohn.org/blog/ web-workers/), в которой он раскрыл особенности встроенного в браузеры механизма "отложенных" вычислений при помощи JavaScript и его будущие перспективы.

    Последняя спецификация Web Workers, несомненно, является наиболее перспективной из грядущих нововведений в браузерах, которая позволяет исполнять JavaScript-задачи параллельно, не блокируя интерфейс браузера.

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

    Имея в виду текущее положение вещей, давайте углубимся в спецификацию Web Workers.

    6.7.1. Web Workers

    Рекомендация Web Worker (http://www.whatwg.org/specs/web-workers/ current-work/) частично основывается на уже проделанной работе со стороны команды Google Gears относительно модуля WorkerPool (http://code.google.com/apis/gears/api workerpool.html). Это идея существенно выросла и была значительно доработана, чтобы стать рекомендацией.

    Worker — это скрипт, который может быть загружен и исполнен в фоновом режиме. Web Workers позволяют легко это сделать, например:

    В этом примере будет загружен скрипт, расположенный по адресу worker.js, а его выполнение будет произведено в фоновом режиме (скорее всего, браузеры будут использовать встроенные средства операционной системы — нити — для реализации такого поведения).

    Однако есть несколько серьезных "подводных камней":

  • Worker не имеет доступа к DOM. Никакого document, getElementById и т. д. (Наиболее важными исключениями из этого правила будут методы setTimeout, setInterval и XMLHttpRequest.)
  • У Worker нет прямого доступа к родительской странице.
  • Имея в виду эти ограничения, стоит сразу задаться вопросом: а для чего, собственно, может пригодиться Worker и какие задачи он способен решать?

    Вы можете использовать Worker, обмениваясь с ним сообщениями. Все браузеры (которые поддерживают данную спецификацию) позволяют обмениваться строковыми сообщениями (Firefox 3.5 также поддерживает обмен JSON-совместимыми объектами). Сообщение может быть как отослано Worker, так и сам Worker может ответить родительской странице таким сообщением. Ниже приведен пример обмена сообщениями.

    Обмен сообщениями производится при помощи API postMessage следующим образом:

    var worker = new Worker("worker.js");
    // Ждем сообщений от worker
    worker.onmessage = function(e){
    // Сообщение от клиента:
    e.data;
    };
    worker.postMessage("start");
    Клиент:
    onmessage = function(e){
    if ( e.data === "start" ) {
    // Выполняем какие-нибудь вычисления
    done();
    }
    };
    function done(){
    // Отправляем полученный результат на главную страницу
    postMessage("done");
    }

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

    Прямо сейчас Web Workers присутствуют в Firefox 3.5 и Safari 4. Они также заявлены в последних ночных сборках Chromium (http://build.chromium.org/buildbot/snapshots/chromium-rel-xp/ ). Наверное, большинство тут же подумают: что нам до возможности, которая доступна лишь для небольшой части веб-пользователей (только в двух современных браузерах!), — но это не должно быть проблемой. Web Workers позволяют эффективно использовать пользовательские машины для параллельных вычислений. В такой ситуации вы можете создавать две версии своих приложений (одну для старых браузеров, и одну — для запуска через механизм Web Workers при его наличии в браузерах). В новых браузерах это просто будет работать значительно быстрее.

    Приведем некоторые занимательные примеры использования данного механизма, которые используют заявленное API.

    6.7.2. Расчет освещения (RayTracing)

    (рис 6.14) Расчет освещения при помощи Web Workers, источник ejohn.org

    Здесь Canvas применен для отрисовки рассчитанной сцены. Если включить Web Workers, то заметно, как картинка отрисовывается по частям. Это происходит благодаря разбиению всей работы на части и поручению каждого набора пикселей отдельному Worker. Этот Worker затем подает массив цветов для отрисовки на Canvas, а родительская страница их применяет. (Заметьте, сам по себе Worker ничего не изменяет.)

    6.7.3. Отслеживание движения

    (рис 6.15) Отслеживание движения при помощи Web Workers, источник: ejonh.org

    В этом случае применяется несколько технологий: элемент video, элемент canvas и отрисовка кадров видео на сам холст. Все отслеживание движения производится в фоновом режиме при помощи Web Workers (по- этому передача видео не останавливается и не блокируется).

    6.7.4. Эмуляция огня

    (рис 6.16) Эмуляция огня при помощи Web Workers, источник: ejohn.org

    Этот пример пытается ограничить несколько случайных точек, используя алгоритм эмуляции огня (simulated annealing). Также здесь приведено анимированное PNG-изображение (работает в Firefox 3.5), которое вращается, пока идет вычисление в фоновом режиме.

    6.7.5. Вычисление при помощи JavaScript Web Workers

    Недавно закончилось интересное соревнование от Engine Yard (http://www.engineyard.com/blog/2009/programming-contest-win-iphone-3gs-2k-cloud-credit/ ). Задание было следующим: необходимо было подобрать фразу, которая бы давала наиболее близкий к исходному SHA1LU, расстояние между хэшами вычислялось по Хэммингу (число отличающихся битов).

    Наибольший интерес представляли два участника, которые попытались решить эту задачу при помощи JavaScript. Их программы запускались в браузере и использовали пользовательский компьютер для проведения вычислений. Скорее всего, никто из них так и не добился впечатляющего результата (если брать во внимание тех кластерных монстров, с которыми пришлось соревноваться), но сам подход вызывает неподдельный интерес.

    В обоих случаях (если верить исходному коду) используется почти одна и та же тактика: рассчитываются несколько результатов, разделяемых по таймеру. Скорость подбора варьируется от браузера к браузеру на уровне 1000-1500 расчетов в секунду. Естественно, эти алгоритмы весьма ресурсоемки и полностью "убивают" пользовательский процессор и "замораживают" интерфейс браузера.

    По-видимому, это является великолепной возможностью применить Web Workers!

    Если взять реализацию Ray C Morgan (http://www.raycmorgan.com/), вырезать весь интерфейс и таймеры и разложить вычисления на 4 параллельных потока для Web Workers, то можно добиться скорости в 4500-9500 расчетов в минуту (в новых браузерах, которые поддерживают механизм Web Workers).

    По этим ссылкам можно посмотреть на демонстрацию (автором которой является Джон Ресиг) и скачать исходные коды:

  • Пример Web Worker для взлома SHA1 (http://ejohn.org/apps/webworkers/)
  • Исходный код для "родителя" (http://ejohn.org/apps/web-workers/run.js)
  • Исходный код для Worker (http://ejohn.org/apps/webworkers/worker.js)
  • 6.8. Клиентские хранилища

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

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

    6.8.1. Cookie

    Первопроходцем на ниве сохранения данных на клиенте можно назвать компанию Netscape, сотрудник которой придумал cookie — сохраняемые на компьютере пользователя в виде "ключ-значение" данные небольшого объема. Объем действительно небольшой, различные браузеры имеют разные ограничения, но даже в лучшем случае их не может быть больше 50, размером не более 4 Кб каждая. Некоторые версии Internet ExpLorer разрешают устанавливать не более 20 cookie, общим объемом не более 4 Кб (в IE 8 — 50 значений, общим объемом 10 Кб).

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

    Например, у популярного веб-сервера Apache есть ограничение на длину каждой строки запроса (директива LimitRequestLine), по умолчанию оно составляет 8 Кб. Отсюда следует, что ограничение сверху на размер cookie (в том худшем случае, если cookie целиком будет состоять из URL-encoded символов) — 2,6 Кб. Конечно, если вы имеете доступ к настройке вашего сервера, это ограничение можно снять.

    Но у cookie есть важное достоинство — к ним можно осуществлять доступ из клиентских скриптовых языков.

    API cookie, если это так можно назвать, очень примитивное. Поскольку использование cookie в браузере можно запрещать, приложению сначала желательно проверить значение свойства navigator.cookieEnabled. Если его значение — истина, то можно прочитать значение document.cookie.

    Значение этого свойства представляет собой строку, где записаны все cookie, объединенные через точку с запятой и пробел. Ключ и значение каждого cookie объединены через знак "равно", впрочем, из этого правила бывают исключения: авторам известны случаи, когда Internet Explorer записывал cookie с пустым значением без "равно".

    У cookie помимо имени есть несколько атрибутов (перечислены под- держиваемые всеми браузерами):

  • expires — время хранения cookie; если этот атрибут не указан, то cookie удалится после закрытия окна браузера, где значение этого cookie было создано. Чтобы удалить cookie, нужно поставить в это поле любую дату прошлого.
  • domain — домен, для которого создается данное значение, остальные домены могут иметь собственные cookie с тем же именем; если параметр не задан, используется текущее имя домена. Допустимые значения — домен, с которого загружен документ, или поддомен этого домена.
  • path — путь, для которого создается значение; по умолчанию используется путь текущей страницы. Путь тут трактуется как директория — значение будет существовать и для всех вложенных путей.
  • secure — флаг, наличие которого означает, что данное значение cookie будет передаваться только по HTTPS
  • Для того чтобы создать новое значение cookie из скриптового клиентского языка, нужно записать в document.cookie строку вида

    key=value; expires=Fri, 31-Dec-2010 23:59:59 GMT; path=/;
    domain=.example.net

    Как было сказано выше, все поля, кроме пары "ключ-значение", можно опустить. Точка перед доменом в параметре domain означает, что это значение будет установлено и для всех доменов выше уровнем. При записи нового значения cookie его значение в указанном домене изменится.

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

    6.8.2. userData behavior

    Компания Microsoft, которая первой начала осваивать веб-приложения в том смысле, в котором мы сейчас используем этот термин, довольно давно предложила программистам механизм для сохранения более существенных объемов данных на компьютере пользователя.

    Уже в Internet ExpLorer 5 появился так называемый userData behavior, собственное расширение Microsoft, которое позволяло хранить до 128 Кб данных в одной записи, общим объемом до 1 Мб, причем в интранете лимит еще более отодвигался — 512 Кб и 10 Мб.

    API по нынешним временам нельзя назвать удобным, но тогда это было частью интересной идеологии behavior, позволяющей разделить данные и логику.

    Данные хранятся в DOM-элементе, у которого, посредством CSS, указан behavior userData:

    function IEStorage (storagename) {
      this.storagename = storagename
        var el = document.createElement('div');
        el.setAttribute('id', 'ourstore-' + storagename);
        el.style.display = 'none';
        el.addBehavior('#default#userData');
        document.body.appendChild(el);
        this.storage = el;
        this.get = function (name) {
          this.storage.load(this.storagename);
          return this.storage.getAttribute(name);
    }
    this.set = function (name, value) {
      this.storage.setAttribute(name, value);
      this.storage.save(this.storagename)
    }
    this.del = function (name) {
      this.storage.removeAttribute(name);
      this.storage.save(this.storagename);
    }
    }

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

    Особенность этого хранилища — можно установить дату устаревания всех его элементов, удалены они будут при вызове load:

    var time = new Date();
    time.setHours(time.getHours() + 1);
    // через час данные будут удалены
    this.storage.expires = time.toUTCString();

    6.8.3. Flash Local Shared Object

    В марте 2002 года появилась шестая версия Flash, популярнейшего плагина для браузеров, установленного, по статистике, на 95% компьютеров. В этой версии появилось собственное хранилище — Local Shared Object, позволяющее хранить до 100 Кб данных без ведома пользователя и любой объем сверх этого с его разрешения. У роликов с одного домена единый Local Shared Object.

    Способ использования этого типа хранилища — установка на странице Flash-ролика, который будет обмениваться данными со скриптовым языком. Вплоть до восьмой версии Flash не существовало хорошего способа обмена данными со скриптовыми языками браузера, пока в Flash8 не появился ExternalInterface, реализованный, впрочем, с существенными ошибками.

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

    6.8.4. WHATWG DB Backend (openDatabase)

    Рабочая группа WHATWG была организована производителями браузеров Apple, Mozilla Foundation и Opera Software ASA. Именно эта группа создала документ Web Application 1.0, который лег в основу пятой версии HTML.

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

    Некоторые версии браузеров, основанных на WebKit — Safari (в том числе под iPhone) и Chromium также содержат openDatabase (именно так называется эта часть спецификации) и представляют собой несложный доступ к реляционной СУБД SQLite из JavaScript. Кроме того, поддержка openDatabase появилась в "Опере" 10.50.

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

    Упрощенный (с минимальной обработкой ошибок) класс для доступа к openDatabase выглядит так:

    function SafariStorage(name, maxsize) {
      this.db = null;
      if (!window.openDatabase) return;
      try {
        this.db = openDatabase(name, '1.0 ', 'Storage for ' + 
        name, maxsize); // maxsize — в байтах
      } catch (e) {
        this.db = null;
        return;
    }
    this.db.transaction(function (t) {
      t.executeSql('CREATE TABLE IF NOT EXISTS storage(k TEXT 
      UNIQUE NOT NULL PRIMARY KEY, v TEXT NOT NULL);', []);
    })
      this.get = function (name, fn, scope) {
        scope = scope || this;
        this.db.transaction(function (t) {
          t.executeSql('SELECT v FROM storage WHERE k = ? '
          [name], function (t, r) {
            if (r.rows.length) {
              fn.call(scope, r.rows.item(0)['v'])
            } else {
              fn.call(scope, null)
            }
          });
      });
    
    }
    this.set = function (name, value) {
      this.db.transaction(function (t) {
        t.executeSql('INSERT OR REPLACE INTO storage(k, v) 
        VALUES (?, ?)', [name, value]);
      });
    }
    this.del = function (name) {
      this.db.transaction(function (t) {
        t.executeSql('DELETE FROM storage WHERE k = ?', 
        [name]);
        });
      }
    }
    }

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

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

    6.8.5. globalStorage и localStorage

    Спецификация на globalStorage присутствует в ранних версиях черновика HTML5, откуда ее удалили из соображений безопасности. Основная идея globalStorage — дать разработчикам возможность обращаться к данным поддоменов, как это сделано в cookie.

    Этот вид хранилища был реализован в Firefox 2 (и в Internet Explorer 8.0 beta 1), но уже в следующей версии браузера возможность обращаться к данным поддоменов отключили. В такой "урезанной" версии globalStorage по возможностям эквивалентно хранилищу localStorage, которое заняло в HTML5 нишу своего небезопасного предшественника.

    В настоящий момент localStorage поддерживается браузерами FireFox 3.5 и выше, Internet Explorer 8.0 (с версии beta 2), Safari 4.0 и выше, а так же Opera 10.50 и выше.

    Ограничения, накладываемые на размер хранилища, устанавливаются разными производителями браузеров по-разному, так, в Firefox это 5 Мб, а в IE 8 — 10 миллионов байт (причем, учитывается место, занятое не только значениями, но и именами ключей). Другое ограничение накладывается на имена ключей, в частности, в них не может быть пробелов.

    function HTML5Storage() {
      if (window.localStorage) {
        this.storage = window.localStorage } else if (window.globalStorage) {
      this.storage = window.globalStorage[location.hostname J ||  'localhost.localdomain '] }   else {
        return false
      }
        this.get = function (name) {
        var out = this.storage.getItem(name); return out  out.value ? out.value : out;
      }
        this.set = function (name, value) { this.storage.setItem(name, value);
      }
        this.del = function (name) {
        this.storage.removeItem(name)
      }
    }

    6.8.6. Google Gears

    Последнее из рассматриваемых нами хранилищ — GoogLe Gears, расширение для браузеров, придуманное GoogLe. В настоящее время встроено в браузер GoogLe Chrome, для других поддерживаемых браузеров скачивается и устанавливается отдельно. Диапазон поддерживаемых браузеров довольно широк: Firefox начиная с версии 1.5, Internet ExpLorer 6.0 и выше, Safari 3.1.1 и выше (для Mac OS), а также мобильные браузеры — IE MobiLe с версии 4.01 и Opera MobiLe 9.51 и выше.

    Основная идея хранилища GoogLe Gears та же, что и у openDatabase, тот же самый доступ к SQLite, но с несколько другим API:

    function GearsStorage(name) {
      if (Iwindow.google || Iwindow.google.gears) { var factory = null; // Firefox
          if (typeof GearsFactory I= 'undefined') {
            factory = new GearsFactory(); } else { // IE try {
          factory = new ActiveXObject('Gears.Factory'); if (factory.getBuildInfo().indexOf('ie_mobile') I= -1) {
        factory.privateSetGlobalObject(this);
      }
    } catch (e) { // Safari
        if ((typeof navigator.mimeTypes I= 'undefined')  navigator.mimeTypes["application/ J x-googlegears"]) 
        {
            factory = document.createElement("object"); factory.style.display = "none"; factory.width = 0; factory.height = 0;
            factory.type = "application/x-googlegears"; document.documentElement.appendChild(factory);
        }
            }
              if (Ifactory) return; if (Iwindow.google) { google = {};
            }
          if (Igoogle.gears) {
              google.gears = {factory: factory};
            }
          }
            this._begin = function () {
            this.db.execute('BEGIN').close();
            }
            this._commit = function () {
            this.db. execute('COMMIT').close();
    }

    Как видно, существенную часть занимает инициализация Google Gears, в каждом браузере она выполняется по-разному. API устроено проще, чем в openDatabase — транзакции нужно создавать самостоятельно, зато есть возможность возвращать данные непосредственно, не прибегая к callback.

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

    6.8.7. Библиотеки для работы с клиентскими хранилищами

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

    PersistJS (http://pablotron.org/?cid=1557 ) — пожалуй, наиболее известная библиотека на этом поприще. Она поддерживает все перечисленные виды хранилищ (правда, Google Gears для IE не поддерживается), но, несмотря на свою известность, обладает рядом существенных недостатков.

    Первый недостаток — нет возможности изменить последовательность, в которой PersistJS перебирает методики хранения данных. К примеру, если вы решили поставить localStorage выше Google Gears, а cookie исключить, вам придется вмешиваться в код библиотеки.

    Другая проблема — если библиотека обнаружила, например, хранилище Google Gears, а клиент ответил отказом на запрос разрешения хранения данных, то PersistJS останется в неопределенном положении.

    Еще недоработка — PersistJS не проверяет готовность Flash-ролика, и в том случае, если вы используете это хранилище, есть небольшая вероятность того, что ваш код попытается обратиться к данным еще до того, как ролик будет загружен. Другая связанная с Flash-роликом проблема — библиотека не умеет запрашивать дополнительное место для хранения данных, если 100 Кб, которые можно использовать без запроса разрешения, исчерпаны.

    Неприятно также, что библиотека позволяет сохранять только текстовые строки, не предоставляя возможности сериализации, впрочем, версия из репозитория умеет представлять сложные объекты в виде JSON.

    Впрочем, у библиотеки хороший плюс — небольшой (около 9 Кб) размер.

    Dojo (http://www.dojotoolkit.org/ ) поддерживает хранилища Flash, Google Gears, globalStorage (Firefox 2.0) и среду запуска веб-приложений Adobe AIR.

    Этот фреймворк лишен недостатков PersistJS, но есть изъян — гигантский размер: версия 1.3.2 занимает 45 Мб. Конечно, для работы с хранилищем весь фреймворк не нужен, но даже минимально необходимый набор занимает более 100 Кб.

    Существует адаптированная версия Dojo Storage, которая называется SRAX Storage (http://fullajax.ru/#:download/), но она поддерживает только Flash Local Shared Object.

    jStore (http://code.google.com/p/jquery-jstore/) — небольшой плагин к jQuery, который поддерживает все рассмотренные хранилища, кроме cookie, обладает умеренным размером (около 15 Кб в минимизированном виде) и свободен от недостатков PersistJS.

    6.8.8. Резюме

    К сожалению, даже создатели HTML5 не предусмотрели возможности адресации к хранилищу посредством URL. Было бы очень удобно, положив в хранилище JavaScript или графическое изображение, подключить содержимое через обычный тег HTML 1 .

    Необходимо помнить, что хранилище — клиентское, и данные, сохраненные в хранилище, не будут доступны клиенту на другой машине и даже (увы) в другом браузере. Так что наиболее подходящее использование клиентских хранилищ — кэширование текстовых данных: JavaScript, CSS,

    Хранилище Ограничение Браузеры
    userData behavior Интранет — 512 Кб каждая запись, 10 Мб на домен, ограниченные узлы — 64 Кб / 640 Кб, остальные — 128 Кб / 1Мб IE 5.0+
    gLobaLStorage 5 Мб FF 2.0+, IE 8 beta 1
    LocaLStorage Firefox, Safari, Opera — 5 Мб Internet ExpLorer — 10 MiB FF 3.5+, Safari 4+, IE 8 beta 2+, Opera 10.50+
    openDatabase 5 Мб Opera 10.50+, некоторые версии Safari 3.xx, 4.xx, GoogLe Chrome 3.xx, 4.xx
    GoogLe Gears Запрашивает разрешение на использование, ограничений по размеру нет Встроен в GoogLe Chrome, плагины для IE 6+, Opera MobiLe 9.51+, FF 1.5+, IE MobiLe 4.01+, Safari 3.1.1+
    FLash Запрашивает разрешение на хранение более 100 Кб данных, ограничений по размеру нет Плагины для IE, FF, GoogLe Chrome, Safari

    1 - создатели GoogLe Gears озаботились доступом к бинарным данным из своего хранилища, изображениями можно манипулировать и выводить в окно браузера при помощи специального расширения, используя тег Canvas. а значит, данных о состоянии приложения и его редко изменяемых ресурсов, которые выгоднее хранить на стороне пользователя. Принцип использования прост: проверяется наличие нужного ресурса в хранилище; если ресурс обнаружен, он загружается из хранилища, если нет, с сервера (например, при помощи AJAX) и сохраняется в хранилище. Далее соответствующий тег с полученным содержимым создается в DOM. К сожалению, браузер Opera не предоставляет никакого встроенного хранилища, хотя в нем можно использовать Flash Local Shared Object; зато современные версии остальных браузеров не нуждаются в установке каких-либо дополнительных плагинов. Всеми браузерами без исключения поддерживаются cookie, но их вряд ли можно рекомендовать в качестве клиентского хранилища из-за сильных ограничений на размер и увеличения исходящего трафика. Из всех рассмотренных библиотек для доступа к хранилищам оптимальнее, на наш взгляд, использовать библиотеку jStore: она достаточно гибкая, небольшого размера и поддерживает все основные виды хранилищ.

    Страницы:

    6.1. Профилируем JavaScript

    Данный раздел написан после прочтения ряда заметок Джона Реси-га (автора JavaScript-библиотеки jQuery), в которых он рассказывал про особенности работы JavaScript в различных браузерах.

    После существенной оптимизации CSS-селекторов и выхода SizzLe (http://sizzLejs.com/), который лишь немного уступает YASS (http://yass.webo.in/), автор jQuery сконцентрировал свои усилия на оптимизации работы с DOM-деревом и наиболее используемых методов и начал искать дополнительные способы для профилирования и оптимизации.

    Также было написано дополнение для глубокого профилирования (http://ejohn.org/blog/deep-profiling-jquery-apps/) jQuery — оно помогло обнаружить методы, которые выполняются чересчур долго на реальных сайтах с jQuery. Дальше было проведено уточнение самих оптимизационных методов, которые, очевидно, являются не такими эффективными, как нам хотелось бы, — ведь непонятно, где именно и что конкретно нужно оптимизировать.

    Для ответа на этот вопрос можно пойти по известному пути и замерить число вызовов функции из какого-либо метода. В этом нам может помочь Firebug, который позволяет увидеть эту информацию в соответствующей вкладке (еще и наряду со значениями времени выполнения каждого метода). К несчастью, очень неудобно вручную вбивать код, затем проверять результаты в консоли и определять, насколько они неудовлетворительны и изменились ли они с прошлого раза. Если бы только существовал способ получать эти числа в автоматическом режиме!

    6.1.1. Профилирующие методы FireUnit

    Джон Ресиг немного улучшил описанную ситуацию и добавил пару новых методов в FireUnit (http://fireunit.org/).

    fireunit.getProfile();

    Можно запустить этот код сразу после того, как вы использовали console.profile() ; и console.profileEnd() ; для перехвата проблемного участка кода, — и получить полный вывод профилирующей информации. Например, если мы запустим это, то получим из fireunit.getProfile() следующий объект JavaScript:

    {
      "time": 8.443,
      "calls": 611,
      "data":[
    {
      "name":"makeArray()",
      "calls":1,
      "percent":23.58,
      "ownTime":1.991,
      "time":1.991,
      "avgTime":1.991,
      "minTime":1.991,
      "maxTime":1.991,
      "fileName":"jquery.js (line 2059)"
      },
    // etc.
    ]}
    fireunit.profile( fn );
    (рис 6.1) Профилирование вызовов JavaScript, источник: ejohn.org

    Второй метод, добавленный в FireUnit, позволяет достаточно просто запускать и профилировать отдельные вызовы функций. Грубо говоря, этот метод запускает профайлер, исполняет функцию, останавливает профайлер, а затем возвращает результаты из getProfile(). Дополнительно он проверяет возможные исключения при вызове функции и гарантирует, что профайлер надежно выключен.

    Это можно использовать примерно следующим образом:

    fireunit.profile(function(){
      document.getElementsByClassName("foo");
    });

    6.1.2. Как это все применять

    Во-первых, обязательно нужно установить последнюю версию (http://github.com/jeresig/fireunit ) FireUnit. Также можно скачать последнюю версию кода в виде расширения к Firefox: http://fireunit.org/fireunit-1.0a1.xpi

    При запуске нужно будет убедиться, что:

  • обе вкладки Console и Script в Firebug включены;
  • свойство extensions.firebug.throttleMessages в about:config выставлено в false.
  • 6.1.3. Результаты

    Ниже приведены результаты вызовов из jQuery 1.3.2 ("методом" называется метод jQuery, который запускается с определенными параметрами, "вызовы" — это число вызовов функции, которые происходят во время работы метода, "O(n)" является грубой оценкой сложности вызова функции):

    Тестовая таблица
    Метод Вызовы O(n)
    .addClass("test"); 542 6n
    .addClass("test"); 592 6n
    .removeClass("test"); 754 8n
    .removeClass("test"); 610 6n
    .css("color", "red"); 495 5n
    .css({color: "red", border: "1px solid red"}); 887 9n
    .remove(); 23772 2n+n 2
    .append("

    test

    ");
    307 3 n
    .append("<p>test</p><p>test</p> <p>test</p><p>test</p><p>test</p>"); 319 3n
    .show(); 394 4n
    .hide(); 394 4n
    .html("<p>test</p>"); 28759 3n+n 2
    .empty(); 28452 3n+n 2
    .is("div"); 110
    .filter("div"); 216 2n
    .find("div"); 1564 16n

    Как можно видеть из этой таблицы по значениям O(n), большинство методов jQuery вызывают по крайней мере по одной функции на каждый элемент, который им нужно обработать. addClass запускает около шести функций на каждый элемент, filter — примерно две, а is — только одну.

    Также мы легко видим проблемные методы, напоминающие большие черные дыры, в которые утекает процессорное время — .remove(), .empty() и .html(). Все они имеют сложности с вызовом функций n 2 , что является значительной проблемой для производительности ваших сайтов. Все эти числа выросли по очень простой причине: .html() использует .empty(), .empty() использует .remove(), а .remove() работает крайне неэффективно. Если не начать профилировать вызовы функций на медленное выполнение (к слову сказать, большинство внутренних методов jQuery выполняются очень быстро), то и не удастся обнаружить код, написанный неэффективно.

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

    Метод Вызовы O(n)
    .remove(); 298 3n
    .html("<p>test</p>"); 507 5n
    .empty(); 200 2n

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

    Также весьма интересно интегрировать описанное профилирование как часть самого процесса разработки — чтобы сразу замечать очевидные недочеты в производительности.

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

    6.2. Проблемы при оценке производительности браузеров

    Данный раздел написан на основе статьи Christian Stockwell, отвечающего за производительность браузера IE. Измерение общей производительности веб-сайтов и браузеров крайне важно для большого числа людей: как для пользователей, использующих различные продукты, так и для разработчиков, оптимизирующих свои порталы. Также сами разработчики других браузеров внимательно следят за успехами их коллег по цеху и стараются ориентироваться на некоторые отраслевые стандарты качества.

    6.2.1. Измерение производительности браузера

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

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

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

    Часть проблем оценки производительности вызвана огромным количеством разнообразных действий, для которых используется браузер. Каждый день пользователи обращаются к широкому диапазону ресурсов — от насыщенного мультимедийным содержимым FLickr до спартанского GoogLe. Они могут столкнуться с интерактивным, насыщенным AJAX-скриптами сайтом, как Windows Live HotmaiL, или сайтом, содержащим лишь статический HTML, как, например, CraigsList, а некоторые из них станут использовать браузер для критически важных деловых приложений (например, построенных на его основе систем электронного документооборота).

    Производительность каждого из этих ресурсов часто зависит от производительности отдельной подсистемы браузера. Например, загрузка насыщенного изображениями сайта может зависеть от скорости, с какой браузер в состоянии загружать и распаковывать изображения. Напротив, производительность простенькой страницы зависит от того, как быстро браузер обрабатывает стандартный HTML. В следующем случае для хорошей производительности насыщенного AJAX-скриптами портала потребуется тесная интеграция JavaScript, CSS и DOM, — и это окажется в большей степени важным, нежели индивидуальная производительность каждого из названных компонентов. Когда на чашу весов кладутся FLash и SiLverLight, производительность будет зависеть от того, насколько хорошо встроены в браузер соответствующие подсистемы управления.

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

    6.2.2. Работа службы кэширования

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

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

    Что это означает в случае оценки производительности браузера? Например, при обращении к ресурсу http://www.microsoft.com ваш браузер может последовательно запрашивать данные из нескольких источников — с прокси-сервера вашей локальной сети, с сервера, расположенного к вам ближе всего, или с нескольких географически удаленных серверов.

    Для повышения скорости загрузки содержимого страниц и распределения нагрузки по сети эти серверы могут сохранять часть загружаемых вами данных у себя в памяти, чтобы остальные пользователи могли быстрее получать к ним доступ. Например, утром, придя на работу, вы первым делом просматриваете новости на http://www.msnbc.com. Скорее всего, браузер попытается сначала загрузить запрашиваемую страницу с прокси-сервера, затем с ближайшего к вам сервера корпоративной сети — перед тем как обратиться к прочим, удаленным от вас ресурсам. Как только страница загрузится, ваш рабочий прокси-сервер или сервер в локальной сети может "решить" (разумеется, в зависимости от предварительно сделанных настроек) сохранить часть ее содержимого. Когда другой пользователь, спустя десять минут, попробует обратиться по тому же адресу, его компьютер сначала получит порцию данных, уже сохраненных на прокси-сервере, вместо их повторной загрузки с удаленных серверов, что, в свою очередь, значительно уменьшит время загрузки страницы и приведет пользователя в прекрасное расположение духа.

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

    Принципы работы системы кэширования изложены здесь очень примитивно. Если требуется детальная информация по этому вопросу, то стоит обратиться к соответствующим ресурсам, включая, собственно, принципы работы НТТР-протокола.

    6.2.3. Размер образца

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

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

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

    Например, давайте посмотрим на таблицу пунктов, набранных двумя браузерами по итогам тестов навигации в пределах одной веб-страницы:

    (рис 6.2) Рис. 6.2. Проблема выбора подходящего среднего, источник: blogs.msdn.com

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

    6.2.4. Совместное использование канала

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

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

    6.2.5. Совместное использование ресурсов

    Совместное использование ресурсов приложениями на вашем компьютере также может повлиять на производительность браузера — по крайней мере так же сильно, как и совместное использование канала.

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

    Результаты тестирования двух браузеров одновременно, "бок о бок", могут оказаться совершенно некорректными. Например, платформа Windows имеет ограничение — возможны лишь 10 одновременных исходящих соединений; остальные запросы будут поставлены в очередь на выполнение по мере освобождения ресурсов и могут, в зависимости от необходимого временного интервала, завершиться успешно или с ошибкой. Такой способ тестирования означает, что вы, скорее всего, поставите один из браузеров в преимущественное положение тем, что запустите его на несколько микросекунд раньше соперника.

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

  • Закройте все прочие приложения, включая те, что скрыты в области уведомлений панели задач. Это особенно важно в случае, если какие-то из этих приложений используют сетевые ресурсы.
  • В командной строке запустите следующую команду для ограничения активности компьютера в процессе тестирования:
  • %windir%\\system32\rundll32.exe advapi32.dll,ProcessIdleTasks

    6.2.6. Взаимодействие с серверами

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

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

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

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

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

    6.2.7. Эффект наблюдателя

    Во многих областях сам факт наблюдения изменяет характер поведения наблюдаемых объектов. Этот феномен получил наименование "эффекта наблюдателя".

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

    6.2.8. "Холодный" старт против "горячего"

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

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

    Чтобы собрать наиболее непротиворечивые данные, откройте и закройте каждый браузер как минимум один раз перед тем, как начнете тестирование. Если все остальные приложения закрыты, это даст вашей операционной системе возможность загрузить нужные компоненты в память и обеспечит последовательность и точность результатов тестирования. Это также создаст равные условия конкуренции для разных браузеров, особенно в свете существования таких функций операционной системы, как Superfetch (http://www.microsoft.com/windows/windows-vista/features/superfetch.aspx), которая в ином случае обеспечит преимущества "любимому" браузеру.

    6.2.9. Содержимое веб-страниц

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

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

    Современные сайты обновляют содержимое очень часто. На Facebook или MySpace вы можете получить два совершенно разных результата между двумя щелчками кнопки мыши по одной и той же ссылке: за это время кто-то добавил запись или комментарий, загрузил картинку или еще каким-то образом изменил содержимое ресурса. На многих сайтах идет постоянная ротация рекламных баннеров, тем самым гарантируя, что любые два входа на сайт будут отличаться друг от друга.

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

    6.2.10. Дизайн страниц

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

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

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

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

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

    6.2.11. "Готово"

    Можете ли вы точно определить, что означает "веб-страница загружена"? Как быть в случае, если она содержит сложные AJAX-сценарии?

    Проблема при оценке производительности заключается в определении того, что, собственно, означает надпись "готово" в статусной строке браузера при загрузке страницы. А также в том, что некоторые страницы усложняются и разрастаются несогласованно друг с другом. Некоторые веб-программисты применяют маркер "загружено" (http://www.w3.org/ TR/htmL401/interact/scripts.htmL#h-18.2.3) как индикатор того, что браузер завершил разметку содержимого страницы для последующей загрузки. Этот маркер, к сожалению, интерпретируется разными браузерами по-разному.

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

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

    6.2.12. Надстройки браузера

    Использование надстроек означает, что с этого момента вы измеряете производительность не только самого браузера. Надстройки могут существенно влиять на производительность. Согласно данным, полученным из источников в Microsoft, IE используется в совокупности с дюжинами надстроек (относительно Mozilla ситуация абсолютно идентичная).

    Любая из этих надстроек может проявлять произвольную активность внутри браузера. Иллюстрацией воздействия может служить следующая ситуация: пользователи, отстаивающие свои предпочтения в отношении определенного браузера, внезапно обнаруживают, что любая альтернативная программа работает быстрее лишь потому, что их любимый браузер перегружен надстройками и дополнениями, а альтернативный представляет собой чистую, без всякого "мусора" программу. Например, пользователь обремененного несколькими дополнениями Firefox может сменить его на IE, увидев, что тот работает быстрее, а в это время пользователь IE переходит на Firefox по той же схеме исходя из тех же причин. Здесь нет никакого противоречия — такие примеры лишь демонстрируют решающее влияние надстроек.

    Для блокировки надстроек в IE 8 необходимо вызвать пункт Manage add-ons из меню Tools. В появившемся диалоговом окне выберите All Addons и последовательно заблокируйте все надстройки из списка. Если вы дружите с командной строкой, можете выполнить команду iexplore.exe -extoff для запуска IE без дополнений.

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

    6.3. Перспективы "быстрого" JavaScript

    В этом разделе рассматривается часть прикладных методов, положенных в основу разработки YASS (http://yass.webo.in/) — самой быстрой библиотеки для выбора элементов по CSS-селекторам.

    6.3.1. Условное ветвление

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

    var a = 1,
      b = 2,
      c = 3;
    if(a == 1) {
      if (b == 2) {
        if (c == 3) {
          ...
        }
      }
    }

    Это, заметим с интересом, работает так же быстро, как и совмещенный if:

    if(a == 1  b == 2  c == 3){
      ...
    }

    Однако последний вариант немного меньше по размеру. Если не стоит задача минимального размера кода, то для улучшения читаемости стоит использовать первый вариант. Если же мы минимизируем все, то можно рассмотреть возможность использования if-then-else выражения. Но нужно иметь в виду, что производительность таких конструкций:

    var v = a == 1 ? b == 2 ? c == 3 ? 1 : 0 : 0 : 0;

    примерно на 10-50% меньше, чем у обычного ветвления, рассмотренного чуть выше.

    В том случае, когда все переменные у нас числовые, проверка равенства их суммы заданной будет выполняться на 5—10% быстрее:

    if (a + b + c == 6) {
      ...
    }

    Если же нам нужно проверить просто существование переменных и их неотрицательность (т. е. то, что переменные не undefined, не NaN, не null, не ‘’ и не 0), то следующий вариант будет работать еще на 5—10% быстрее, чем предыдущий случай (и на 10—20% быстрее, чем самый первый пример):

    if (a  b  c) {
      ...
    }

    Очень часто нам нужно проверить что-то более сложное, чем просто число. Например, совпадение строки с заданной или равенство объектов. В этом случае нам просто необходимо следующее сравнение:

    var a = 1,
      b = 2,
      c = '3';
    if (a == 1  b == 2  c === '3') {
      ...
    }

    Здесь мы используем сравнение без приведения типов ===, которое в случае нечисловых переменных работает быстрее обычного сравнения на 10—20%.

    6.3.2. Выбор в зависимости от строки

    Достаточно часто нам нужно выбрать одну из условных ветвей, основываясь на заданной строке. Обычно для этого используются либо методы объекта RegExp (exec, test), либо строковые методы ( match, search, indexOf ). Если нам нужно просто проверить соответствие строки какому-то регулярному выражению, то лучше всего для этого подойдет именно test:

    var str = 'abc',
      regexp = new RegExp('abc');
    if (regexp.test(str)) {
      ...
    }

    Такая конструкция отработает на 40% быстрее, чем аналогичный exec:

    if (regexp.exec(str)[1]) {
      ...
    }

    Строковый метод match аналогичен методу exec у создаваемого объекта RegExp, но работает на 10—15% быстрее в случае простых выражений. Однако метод search работает чуть медленнее (5—10%), чем test, потому что последний не возвращает найденную подстроку.

    В том случае, если регулярное выражение требуется "на один раз", подойдет более быстрая (примерно на 10% относительно варианта с инициализацией нового объекта) запись:

    if (/abc/.test(str)) {
      ...
    }

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

    if (str.indexOf('abc') != 1) {
      ...
    }

    6.3.3. Точное соответствие и хэши

    Давайте теперь рассмотрим следующий вариант регулярного выражения: /alblc/.В этом случае нам нужно проверить в заданной строке наличие одного из возможных вариантов (или равенство строки этому варианту). В случае точного соответствия быстрее регулярного выражения (на 50%) будет проверка строки как ключа какого-либо хэша:

    var hash = {'a':1, 'b':1},
    str = 'a';
    if (h[str]) {
      ...
    }

    Быстрее (на 20%) такого хэша будет только точная проверка строки на определенные значения:

    if (ss === 'a' || ss === 'b'){
      ...
    }

    Если рассмотреть 3 конструкции: вложенный if, switch с соответствующим значением и проверка значений в хэше, — то стоит отметить следующую интересную особенность. При небольшом уровне вложенности if (если всего значений немного или мы очень часто выходим по первому-второму значению), конструкции if и switch обгоняют по производительности хэш примерно на 10%. Если же у нас значений много и они все примерно равновероятны, то хэш отрабатывает в общем случае быстрее уже на 20%. Это в равной степени относится как к установлению значений переменных, так и к вызову функций. Поэтому для создания ветвления с вызовом функций лучше всего использовать именно хэш.

    При анализе CSS-селектора можно выделить несколько подзадач, описываемых как "проблема выбора".

  • Ветвление для простого случая выполнено при помощи проверки входной строки через test:
    if (/^[\w[:#.][\w\]*^|=!]*$/.test(selector)) {
      ...
    } else {
      ...
    }
  • Ветвление для простейшего случая (когда нам нужно выбрать по идентификатору или по классу). Поскольку всего значений у нас 5 и 3 из них относительно равновероятны (выбор по классу, по идентификатору или по тегу), используется switch:
    switch (firstLetter) {
      case '#':
    ...
      break;
      case '.':
      ...
      break;
    case ':'
      ...
      break;
    case '[':
      ...
      break;
    default:
      ...
      break;
    }
  • Абсолютно аналогичную картину мы наблюдаем для выбора правильного отношения "родитель-ребенок" (>, +, $$\sim$$, ): тут тоже только switch:
    switch (ancestor) {
      case ' ':
      ...
      break;
    case '∼':
      ...
      break;
    case '+':
      ...
      break;
    case '>':
      ...
      break;
    }
  • Наконец, выбор соответствующей проверочной функции для child-модификаторов ( first-child, last-child, nth-child, и т. д.) и выбор проверочной функции для атрибутов ( $$\sim$$ =, *=, = и т. д.) осуществляется уже через специальные хэши:
    _.attr = {'': ... , '=': ... , '=': ... , '^=': ... ...}
  • 6.3.4. Итоговая таблица

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

    Задача Средство решения
    Проверка числового значения Обычное сравнение (==)
    Проверка нескольких числовых значений Сравнение их суммы
    Проверка, что число не нуль, или проверка на существование Проверка отрицания к заданной переменной (!)
    Разбор строки и выделение частей в массив String.match(RegExp) или RegExp.exec(String)
    Проверка строки на соответствие регулярному выражению RegExp.test(String)
    Проверка строки на точное соответствие (либо соответствие одному из набора значений) if без приведения типов (===)
    Выбор в зависимости от точного значения (значений 1—2) Условная конструкция if
    Выбор в зависимости от точного значения (значений 3—8) switch
    Выбор в зависимости от точного значения (значений больше 8) Хэш с ключами, соответствующими значениям

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

    ( http://webo.in/articles/habrahabr/78-javascript-constructionsperformance/ ) и сделать соответствующие выводы.

    6.4. Реализация логики CSS3-селекторов

    На данный момент насчитывается несколько десятков различных JavaScript-библиотек, которые предлагают механизм выбора элементов по CSS-селектору. В чем же разница между ними всеми? Разница в скорости нахождения элементов. Иногда она может варьироваться довольно сильно.

    Но в последнее время появилось несколько явных фаворитов на этом поприще. Речь идет про Sizzle (движок выборки элементов, автором которого является Джон Ресиг и который включен в jQuery 1.3+), Peppy (достаточно хорошо стартовавший и обогнавший на первых порах Sizzle, но потом заброшенный автором) и некоторые другие, в том числе и YASS (http://yass.webo.in/).

    Поэтому возникает резонный вопрос, почему нельзя сделать быстрое мини-ядро для CSS-селекторов, которое обеспечит базовую функциональность для работы с DOM (например, совсем базовую — просто выборку элементов)? И, самое главное, чтобы это работало не медленнее (в идеале даже быстрее), чем вызовы в самом браузере.

    6.4.1. Основы быстродействия

    Но описанная задача имеет решение. Можно создать библиотеку, которая будет осуществлять базовые операции практически так же быстро, как и сам браузер (а в некоторых случаях даже быстрее — за счет кэширования). И это удалось сделать в достаточно сжатые сроки. Далее речь пойдет о самой быстрой (на момент написания книги) библиотеки для выбора элементов по CSS-селекторам. Каковы же были причины для написания такой библиотеки?

    Во-первых, такой код должен и кэшировать выборки (DOM-вызовы дорого обходятся, все нормальные JavaScript-программисты уже их кэши-руют — так упростим им задачу и повысим быстродействие).

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

    В-третьих, библиотека должна возвращать сами элементы, чтобы к ним можно было "прикрутить" любые обертки и нарастить методы. Все обертки ресурсоемки. Если нужно просто 200 раз поменять HTML на странице у заданных элементов, то они не нужны. Достаточно и проверок со стороны браузера на допустимость выполняемых операций.

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

    6.4.2. Примеры вызовов

    Синтаксис такой библиотеки до безобразия прост:

  • _(‘p’) — вернет все параграфы на странице;
  • _(‘p a’) — или все ссылки в них;
  • _(‘p a.blog’) — или все ссылки с классом blog.
  • 6.4.3. Еще один велосипед?

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

    Или же данный код можно будет доработать и включать в основу высокопроизводительных библиотек, которые уже будут при его помощи ре-ализовывать свои методы (одной из таких библиотек, с которыми YASS уже интегрирован, является js-core, http://code.googLe.eom/p/js-core/). Также возможна замена кодом YASS встроенного механизма выборки элементов по CSS-селектору в таких распространенных библиотеках, как MooTooLs, Prototype, jQuery, YUI для повышения их быстродействия.

    Но давайте рассмотрим процесс выборки CSS-селекторов более детально.

    6.4.5. Выборка CSS-селекторов

    Начнем с самого простого: чего мы хотим добиться? Мы хотим, задав произвольную строку CSS-селектора, соответствующую спецификации (http://www.w3.org/TR/2005/WD-css3-seLectors-20051215/), получить на выходе массив из всех элементов, соответствующих этой самой строке. Вроде пока все просто.

    В качестве иллюстрации спецификации можно привести следующие примеры (работает во всех современных браузерах и IE8+):

    // вернет элемент с идентификатором my_id
    querySelectorAll('#my_id')
    // вернет все элементы с классом external
    querySelectorAll('.external')
    // вернет все абзацы на странице
    querySelectorAll('p')

    Однако уже тут можно отметить один момент: очень часто нам нужно выбрать просто элемент по его идентификатору или найти все элементы с определенным классом. Эти операции встречаются достаточно часто во всех JavaScript-библиотеках, поэтому они должны выполняться максимально быстро. Запускать весь механизм анализа входной строки селектора просто в том случае, когда нам нужно вернуть один-единственный элемент, заданный с помощью идентификатора, крайне неосмотрительно. Здесь мы можем воспользоваться принципом ленивого программирования: "не делай того, чего можно не делать", — и достаточно сильно ускорить работу для простейших случаев.

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

    Может так случиться, что регулярное выражение обеспечивает корректность, и замена его никак не приведет к потере читаемости кода и непонятному выигрышу (или даже проигрышу) в скорости выполнения. Тогда можно заменить, например, exec на связку test и charAt / substr: это позволит увеличить производительность примерно на 20%. Если данный участок кода выполняется в цикле многократно, ускорение может оказаться достаточно существенным.

    В YASS данная задача решена следующим образом:

    // проверяем, удовлетворяет ли селектор простому случаю
    if (/^[\w[:#.][\w\]*^|=!]*$/.test(selector)) {
    // в случае положительного результата инициализируем переменную,
    // которая отвечает за '#', '.' , ':' или '[' в начале селектора
      var firstLetter = selector.charAt(0);
      ...
    }

    6.4.6. От простого к сложному

    Но давайте рассмотрим, как решена общая задача по разбору CSS-селекторов. Если принять во внимание, что селектор может быть задан в виде p a.link, form input[type=radio], то логику его разбора можно схематично записать в следующем виде:?

  • Выбираем последовательности селекторов, которые находятся между запятыми. Далее работаем с каждой последовательностью в отдельности. На выходе все последовательности объединяем в итоговый массив ( sets ).
  • В последовательности селекторов у нас есть набор элементарных селекторов, которые "вложены" друг в друга (для нашего примера это p a.link ). Нам нужно разбить последовательность на части и разобрать каждую такую часть, учитывая, что родительскими элементами для следующей части будут выбранные элементы из предыдущей. За "превращение" дочерних узлов в родительские (прямо процесс взросления получается) отвечает массив nodes.
  • Каждый элементарный элемент уже придется разбирать с помощью регулярного выражения, чтобы вычленить части, отвечающие за идентификатор, класс и модификаторы CSS 2/3. Разбирать быстрее всего при помощи exec, а потом записывать в переменные части полученного массива:
    single = regexp.exec(single);
    tag = single[1];
    id = single[2];
    ...
  • И наконец, третий цикл проходится по всем родительским элементам и пытается выбрать из них дочерние узлы, соответствующие заданным в CSS-селекторе параметрам.
  • Как мы видим, основная логика данной задачи включает как мини- мум одно регулярное выражение (использование indexOf и substring будет при такой сложности намного более ресурсоемко) и 3 цикла (которые нужно сделать максимально быстрыми). Не стоит перечислять все возможности быстрого выбора элементов, просто сделаем акцент на некотоых аспектах.

    6.4.7. Перебор массива

    Пусть у нас объявлен некоторый массив a, с элементами которого мы совершаем какие-либо действия. Нам нужно перебрать все элементы строго по возрастанию (порядок важен), т. е. просто while(i-) мы использовать не можем. Наиболее распространенным сейчас способом будет обычный for:

    for (var j=0, item = a[j]; item; item = a[j++]) { 
      item++;
    }
      Естественно, он на 30-40% медленнее следующего while: 
      var j = 0,
      item,
    len = a.length; 
    while (j < len) 
    { item = a[j++]; 
    item++;
    }

    Однако если нам нужно выполнить какие-либо действия с элементом массива, то без кэширования его в локальную переменную никак не обойтись. В этом случае следующий вариант с while (через проверку существования элементов при инкременте) будет еще быстрее — на 5—10%:

    var j = 0,
      item;
    while (item = a[j++]) {
      item++;
    }

    Очевидно, что для всех трех циклов в YASS (http://yass.webo.in/) применяется именно он.

    Если же нам абсолютно не важен порядок элементов (например, просто нужно найти нужный или вернуть false), то логично будет воспользоваться обратным while:

    while (idx—) {
      sets[idx].yeasss = null;
    }

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

    6.4.8. Уникальность элементов

    Одной из основных проблем в ходе выбора элементов по CSS-селек-тору является корректность конечного набора. В случае div p у нас могут оказаться вложенные div, и если мы просто переберем все родительские элементы и объединим получившиеся дочерние, то конечный набор будет содержать дубли. Чтобы избежать такого рода ошибок, нам нужно как-то отмечать элементы, которые мы выбрали.

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

    Схематично представить работу данного флага можно следующим образом:

    for (child in children) {
      if (!children[child].yeasss) { if (last) 
      {children[child].yeasss = 1;
      }
      newNodes = children[child];
       }
    }

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

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

    Сброс флага (о котором шла речь в предыдущем разделе) осуществляется уже для окончательного массива элементов (после завершения всех циклов) и необходим как для корректности дальнейших выборок, так и для предотвращения утечек памяти в IE (расширенные свойства элементов вызывают микро-утечки в IE 6/7).

    6.4.9. Подводя черту

    YASS (http://yass.webo.in/) создавалась и продолжает разрабатываться для поиска и реализации наиболее производительных методов для решения определенного круга задач. Ее можно применять как в учебных целях, так и в чисто практических (например, для составления набора неиспользуемых на сайте CSS-селекторов — с помощью YASS это реализуется быстрее всего).

    6.5. API для CSS-селекторов в браузерах

    Данный раздел написан под впечатлением от статьи DOM selectors API in Firefox 3.5 (http://hacks.mozilla.org/2009/06/dom-selectors-api/) Джон Ресиг (автора jQuery и евангелиста веб-стандартов в Mozilla), в которой освещается текущая поддержка браузерами стандартов в этом направлении и некоторые вопросы производительности.

    Предварительная версия документа API для селекторов (http://dev.w3.org/2006/webapi/selectors-api/), опубликованная консорциумом W3C, представляет собой относительно новый взгляд для JavaScript-разработчиков на то, как можно выбирать DOM-элементы на страницы при помощи CSS-селекторов. В одном этом документе собраны все тонкости такого сложного процесса, как поиск, выборка элементов из DOM-дерева и представление результата, доступного по упорядоченному интерфейсу.

    Несмотря на все недавние войны по поводу интеграции стандартов в браузеры, этот является одним из наиболее поддерживаемых: его можно использовать прямо сегодня в браузерах Internet ExpLorer 8, Chrome и Safari, а также в Firefox 3.5 и Opera 10.

    6.5.1. Используем querySelectorAll

    API для селекторов предоставляет два метода для всех DOM-доку-ментов, элементов и фрагментов (удивительно удобно, хотя два — это, может быть, немного перебор, но иначе получение уникальных элементов на странице каждый раз выливалось бы в дополнительный JavaScript-код): querySelector и querySelectorAll. Оба метода практически идентичны: оба принимают CSS-селектор и возвращают DOM-элементы (за исключением того, что querySelector возвращает только первый найденный элемент).

    Например, давайте рассмотрим следующий участок HTML-кода:

    <div id="id" class="class">
    <p>Первый абзац.</p>
    <p>Второй абзац.</p>
    </div>

    Мы можем использовать querySelectorAll, чтобы сделать красным фон всех параграфов внутри div с идентификатором id.

    var p = document.querySelectorAll("#id p");
    for ( var i = 0; i < p.length; i++ ) {
    p[i].style.backgroundColor = "red";
    }

    А также мы можем найти самый первый параграф этого div, который является его прямым потомком и у которого задан класс class. Ему мы присвоим класс first.

    document.querySelector("div.class > p:first-child")
    .className = "first";

    В повседневной жизни описанные процедуры могут быть весьма запутанными в связи с большим объемом JavaScript-/DOM-кода, приводя к многострочным записям и множеству выборок для достижения какой-либо цели. Сразу стоит отметить, что хотя производительность CSS-селекторов уже интегрирована в браузеры, но ее быстродействие (особенно для ряда сложных случаев CSS3-спецификации) может быть весьма низкой.

    Для преодоления этой проблемы необходимо использовать кэширующие техники, которые реализованы, например, в YASS (http://yass.webo.in/).

    Хотя внешне применение методов API для селекторов весьма просто (каждый принимает только один аргумент на вход), проблемы наступают при выборе подходящей спецификации CSS-селекторов. API для селекторов привязано (и это на самом деле очень хорошо: представьте ситуацию, что браузер в CSS-коде понимал бы один набор селекторов, а при использовании JavaScript предоставлял бы уже совершенно другой доступный набор) к естественному движку CSS-селекторов в браузере, который нужен для применения стилей для конкретных элементов. Для большинства браузеров (Firefox, Safari, Chrome и Opera) это означает, что у вас есть доступ к полной гамме CSS3-селекторов. В то же время Internet Explorer 8 обеспечивает более ограниченный функционал и поддерживает только CSS2-селекторы (работать с которыми до сих можно только с трудом в силу отсутствия их полноценной поддержки в IE 6/7).

    Итак, самой большой проблемой для новых пользователей API для селекторов является выбор корректной CSS-спецификации для использования. Особенно это актуально для большинства разработчиков, который пишут кроссбраузерный код и поэтому ориентируются на CSS1-набор, работающий во всех браузерах.

    Изучение спецификаций CSS2- (http://www.w3.org/TR/CSS2/selector.html) и CSS3-селекторов (http://www.w3.org/TR/css3-selectors/) будет отличным шагом в увеличении своего багажа знаний.

    6.5.2. Суровая реальность

    Наиболее часто встречающийся случай применения API для CSS-се-лекторов — это использование его не напрямую, а при помощи разнообразных сторонних библиотек, которые также обеспечивают функциональность CSS-селекторов для DOM. Сегодня основная проблема внедрения применения API для селекторов заключается в том, что они не поддерживаются во всех браузерах, для которых ведется разработка (в частности, это IE 6, IE 7 и Firefox 3). Поэтому пока эти браузеры еще не вышли из обращения, нам будут требоваться некоторые промежуточные утилиты для восстановления недостающей функциональности CSS-селекторов для DOM.

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

    Некоторые из существующих фреймворков, использующих по возможности API для селекторов:

  • jQuery (http://jquery.com/)
  • Prototype (Prototype (http://prototypejs.org/))
  • Dojo (Dojo (http://dojotooLkit.org/))
  • MooTooLs (http://mootooLs.net/)
  • Стоит также подчеркнуть, что применение нового API влечет значительный выигрыш в производительности (по сравнению с обычными методами выбора элементов из DOM при помощи JavaScript). Вы сможете самостоятельно убедиться в этом, просто судя по улучшению ситуации в JavaScript-библиотеках, которые начали внедрять новое API для селекторов.

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

    (рис 6.3) Прирост в производительности после внедрения API для селекторов, источник: hacks.mozilla.org

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

    6.5.3. Тестовый набор

    Чтобы сравнить определение спецификации API для селекторов (http://dev.w3.org/2006/webapi/seLectors-api/) с фактической реализацией, было создано специальное тестовое окружение (автор — Джон Ресиг из MoziLLa). Это тестовое окружение может быть в том числе использовано для проверки основных браузеров на уровень соответствия стандартам.

    Текущие результаты для браузеров, которые поддерживают это API, следующие:

  • Firefox 3.5: 99,3%
  • Safari 4: 99,3%
  • Chrome 2: 99,3%
  • Opera 10b1: 97,5%
  • Internet Explorer 8: 47,4%
  • Internet Explorer 8, как уже было упомянуто ранее, не реализует логику CSS3-селекторов (наверное, в силу того, что спецификация еще не утверждена w3.org), поэтому проваливает большую часть тестов.

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

    6.6. Canvas: один шаг назад, два шага вперед

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

    6.6.1. Предыстория

    В 1998 году компания Microsoft при поддержке других крупных производителей предложила W3C свой стандарт отображения векторной информации — VML (англ. Vector Markup Language — векторный язык разметки). Он был основан на текущем формате представления документов в вебе — HTML — и расширял его до некоторого "векторного" языка. Компания Microsoft активно продвигала и продвигает данный формат и включает его во все версии IE начиная с 5.0. В рамках стандарта он окончательно закреплен в качестве части спецификации Open Office XML (ISO 29500:2008 и ECMA-376) в 2008 году.

    В этом же 1998 году Adobe, IBM, Netscape и Sun вносят в W3C предложение о рассмотрении своего стандарта в противовес Microsoft — PGML (англ. Precision Graphics Markup Language — точный графический язык разметки). Не желая делать ни один стандарт проприетарным, W3C создает рабочую группу, которая на основе имеющихся предложений в 1999 году создает набросок еще одного стандарта — SVG (англ. Scalable Vector Graphics — масштабируемая векторная графика). Этот стандарт (хотя до сих пор закрепленный только в форме рекомендации, последняя версия 1.1 от 2003 года) находит гораздо большую поддержку у производителей браузеров и на данный момент включен практически везде (кроме, естественно, IE).

    Сейчас большинство JavaScript-библиотек, которые предлагают работу с векторной графикой, включают поддержку SVG для всех браузеров и поддержку VML для IE. В качестве характерного примера можно привести Яндекс.Карты или Google Maps. Оба формата (SVG и VML) обладают практически одинаковыми возможностями; например, ниже приведен код на VML для отображения синего овала:

    <html xmlns:v="VML">
      <style type="text/css">
        v\:*{behavior:url(#default#VML);position:absolute}
      </style>
      <body>
        <v:oval style="left:0;top:0;width:100;height:50"
        fillcolor="blue" stroked="f"/>
      </body>
    </html>

    Этот же код на SVG:

    <?xml version="1.0"?>
            <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
    "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
            <svg xmlns="http://www.w3.org/2000/svg" width="100" height="50">
              <ellipse cx="50" cy="25" rx="50" ry="25" fill="blue"
              stroke="none" />
            </svg>

    6.6.2. Появление Canvas

    Спецификация Canvas (как отдельной области на странице, внутри которой можно отображать графические объекты) в 2005 году изначально была предложена со стороны Apple для поддержки некоторых приложений внутри движка WebKit (на данный момент его используют браузеры Safari и Chrome). Рабочая группа W3C включила Canvas в Web Applications 1.0, который вошел в готовящийся стандарт HTML 5.0.

    Сейчас встроенная поддержка Canvas реализована в том или ином виде во всех современных браузерах. В IE версии 8 и ниже она эмулируется при помощи отдельного VML-документа.

    В качестве основного отличия от VML и SVG стоит назвать принципиальную невозможность описать какой-либо элемент в Canvas, используя строго HTML и CSS: все действия производятся только при помощи JavaScript. Также стоит упомянуть, что SVG оперирует с отдельными векторными объектами и их взаимосвязями, тогда как Canvas предлагает интерфейс к пиксельной области на странице, не предлагая устанавливать дополнительных связей (все они находятся в зоне ответственности JavaScript).

    6.6.3. Основные возможности

    Canvas предлагает достаточно мощное API для действий над двумерными графическими объектами. В том числе, это вывод и преобразование текста (в том числе загрузка произвольного шрифта), отображение двумерных объектов (прямоугольников, треугольников, кругов и многоугольников), работа с векторной графикой (задается как объект пути), преобразование над фигурами (прозрачность, перемещение, поворот и даже матрица преобразования), работа с отдельными линиями и тенями. Также возможна работа с изображениями в попиксельном формате (что позволяет сделать практически полноценный Photoshop внутри отдельного браузера) и коррекция цвета.

    Давайте рассмотрим следующий простой пример использования Canvas:

    <html>
      <head>
        <script type="application/x-javascript">
    function draw() {
      var canvas = document.getElementById("canvas");
      var ctx = canvas.getContext("2d");        
      
      ctx.fillStyle = "rgb(200,0,0)";
    ctx.fillRect (10, 10, 55, 50);
    ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
    ctx.fillRect (30, 30, 55, 50);
    }
    window.onload = draw;
    </script>
    </head>
    <body>
    <canvas id="canvas" width="300" height="300"></canvas>
    </body>
    </html>

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

    (рис 6.4) Пример простого изображения при помощи Canvas,источник: developer.mozilla.org

    Дополнительно Canvas позволяет определить практически любые действия пользователя над описываемыми объектами (перемещение и нажатия мыши) и загрузить в область объекты MathML и SVG. Как мы видим, на данный момент это полноценная платформа для произвольной анимации прямо в том же браузере, который предназначен для просмотра отдельных HTML-страниц. Можно с уверенностью предсказать, что через пару лет обычные FLash-банеры будут вытеснены их более интерактивными и более "поддерживаемыми" коллегами на основе Canvas (и, скажем, VML для семейства браузеров IE).

    Подробнее со спецификацией Canvas можно ознакомиться, например, на странице WHATWG (http://www.whatwg.org/specs/web-apps/ current-work/muLtipage/the-canvas-eLement.htmL).

    6.6.4. Примеры использования Canvas, SVG и VML

    Примеров использования Canvas довольно много, и они частично охватывают уже известные области 2D-графики.

    2D-проекции 3D-объектов

    С помощью Canvas можно изображать 2D-проекции трехмерных объектов. В силу того, что все преобразования выполняются на пиксельном уровне, достаточно просто создать необходимые интерфейсы для изометрических проекций трехмерных объектов. Видимо, уже скоро на основе Canvas появятся полноценные 3D-экскурсии в браузерах или онлайн-игры "от первого лица".

    (рис 6.5) Изображение чайника при помощи Canvas, источник: www.nihilogic.dk

    Typeface.js

    В качестве следующего применения стоит упомянуть библиотеку typeface.js (http://typeface.neocracy.org/) и созданный на ее основе метод отображения произвольных шрифтов на сайте (ранее для этой цели активно использовался Flash). Единственным минусом данного подхода (на фоне повышенного быстродействия в связи с естественной поддержкой Canvas и простоты использования) является большой размер файла самих шрифтов.

    Для применения библиотеки typeface.js необходимо перевести требуемый шрифт в некоторый объект, описываемый с помощью соответствующих конструкций на JavaScript, затем этот объект будет использоваться для представления произвольного текста на сайте (если быть точным, то в качестве объекта выступают отдельные символы шрифта). Размер файла шрифта в текстовом формате сильно варьируется от количества включенных в него символов и может составлять от 50 до 500 Кб (в неархивированном виде).

    Для больших порталов (где размер страницы составляет 500-1000 Кб) данный подход вполне приемлем (если "стилизованных" заголовков будет не так много, чтобы лишний раз не нагружать браузер преобразованиями страницы). Для небольших сайтов загрузка нескольких десятков Кб JavaScript-кода для стилизации 10 Кб HTML- и CSS-кода выглядит не очень уместной.

    (рис 6.6) Применение Canvas для стилизации шрифтов, источник typeface.neocracy.org

    Cufo’n

    Cufo’n в качестве основы использует для отображения произвольных шрифтов уже SVG. Однако проблемы с конвертацией файлов шрифтов в промежуточный формат присутствуют и здесь.

    Более подробно ознакомиться и загрузить необходимые файлы можно по адресу http://cufon.shoqolate.com/generate/.

    Prosessing.js

    Processing — язык программирования, созданный Casey Reas и Benjamin Fry в академических целях и направленный на кроссплатформенную обработку двумерных графических объектов. Он может быть реализован на любой аппаратной платформе путем преобразования исходных конструкций в платформозависимые инструкции.

    Хорошим примером использования Canvas является реализация Processing для JavaScript (автор реализации — Джон Ресиг) — библио- тека Processing.js (processingjs.org/). Она предполагает полную совместимость инструкций данного языка с преобразованиями объекта Canvas. На данный момент доступно несколько проектов, применяющих Processing.js, в частности, несколько игр в браузерах, например, "Защи- та башнями".

    (рис 6.7) Использование processing.js для игры "Защита башнями", источник: willarson.com

    Raphaеl

    Если предыдущий пример был посвящен использованию Canvas больше в развлекательных целях, то библиотека Raphael.js (http://raphaeljs.com/) преследует сугубо практические цели (хотя и делает это с помощью SVG + VML). С ее помощью можно удобно и красиво представлять различные объемы данных во всем привычном формате графиков.

    Применение этой библиотеки предельно просто: обычно нужно объявить необходимые данные и задать один из множества доступных представлений (или создать свое собственное). Более подробно с данной библиотекой можно ознакомиться на ее официальном сайте —http://raphaeljs.com/.

    6.6.5. Проблемы быстродействияя

    На данный момент Canvas при решении большинства задач справляется быстрее, чем SVG. Достаточно давно был разработан пример использования Canvas для ряда задач GoogLe Maps (http://www.ernestdeLgado.com/gmaps/canvas/ddemo1.htmL). В нем зафиксирован прирост скорости в 200500% (для всех браузеров, которые поддерживают Canvas).

    (рис 6.9) Пример использования Raphael.js для отображения данных, источник: raphaeljs.com(рис 6.8) Быстродействие Canvas, источник: www.ernestdelgado.com(рис 6.11) Возможности и быстродействие Canvas, источник: prototype-graphic.xilinus.com(рис 6.10) Сравнение быстродействия Canvas и SVG, источник: intertwingly.net

    В результате другого тестирования (http://prototype-graphic.xilinus.com/samples/shape.html ) Canvas также демонстрирует значительное преимущество перед SVG, но менее широкий набор возможностей.

    В качестве примера можно привести еще один тест (http://intertwingly.net/ stories/2006/07/10/penroseTiling.html) скорости отображения объектов в Canvas и SVG. Здесь SVG снова проигрывает (но совсем незначительно, в большинстве браузеров разницы почти нет).

    Для уточнения вопросов производительности можно обратиться к исследованию (http://www.borismus.com/canvas-vs-svg-performance/), установившему закономерность между производительностью SVG, Canvas и параметрами изображения. В результате оказывается вполне очевидным, что при увеличении числа объектов (для SVG — векторных) производительность SVG падает сильно (почти экспоненциально), а Canvas остается на стабильном уровне. Здесь стоит отметить, что размер активной области при этом не изменяется.

    (рис 6.12) Производительность Canvas и SVG при увеличении числа объектов, источник: www.borismus.com

    Однако если мы начнем увеличивать область построения (размеры объектов), то тут векторный формат показывает себя во всей красе: производительность практически не меняется. Производительность Canvas падает (как и следовало ожидать) квадратичным образом от числа объектов (площадь активной области увеличивается квадратично).

    (рис 6.13) Производительность Canvas и SVG при увеличении размера объектов, источник: www.borismus.com

    Из этого можно сделать простой вывод: если вы собираетесь использовать точечную (пиксельную) графику, то лучше Canvas для этой цели ничего не подходит. При работе с большими (по площади) векторными объектами лучше применять SVG. Также будет необходимо дублировать всю функциональность через VML для IE 8 и ниже.

    6.7. Вычисляем при помощи Web Workers

    Данный раздел написан под впечатлением статьи Джона Ресига "Computing with JavaScript Web Workers" (http://ejohn.org/blog/ web-workers/), в которой он раскрыл особенности встроенного в браузеры механизма "отложенных" вычислений при помощи JavaScript и его будущие перспективы.

    Последняя спецификация Web Workers, несомненно, является наиболее перспективной из грядущих нововведений в браузерах, которая позволяет исполнять JavaScript-задачи параллельно, не блокируя интерфейс браузера.

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

    Имея в виду текущее положение вещей, давайте углубимся в спецификацию Web Workers.

    6.7.1. Web Workers

    Рекомендация Web Worker (http://www.whatwg.org/specs/web-workers/ current-work/) частично основывается на уже проделанной работе со стороны команды Google Gears относительно модуля WorkerPool (http://code.google.com/apis/gears/api workerpool.html). Это идея существенно выросла и была значительно доработана, чтобы стать рекомендацией.

    Worker — это скрипт, который может быть загружен и исполнен в фоновом режиме. Web Workers позволяют легко это сделать, например:

    В этом примере будет загружен скрипт, расположенный по адресу worker.js, а его выполнение будет произведено в фоновом режиме (скорее всего, браузеры будут использовать встроенные средства операционной системы — нити — для реализации такого поведения).

    Однако есть несколько серьезных "подводных камней":

  • Worker не имеет доступа к DOM. Никакого document, getElementById и т. д. (Наиболее важными исключениями из этого правила будут методы setTimeout, setInterval и XMLHttpRequest.)
  • У Worker нет прямого доступа к родительской странице.
  • Имея в виду эти ограничения, стоит сразу задаться вопросом: а для чего, собственно, может пригодиться Worker и какие задачи он способен решать?

    Вы можете использовать Worker, обмениваясь с ним сообщениями. Все браузеры (которые поддерживают данную спецификацию) позволяют обмениваться строковыми сообщениями (Firefox 3.5 также поддерживает обмен JSON-совместимыми объектами). Сообщение может быть как отослано Worker, так и сам Worker может ответить родительской странице таким сообщением. Ниже приведен пример обмена сообщениями.

    Обмен сообщениями производится при помощи API postMessage следующим образом:

    var worker = new Worker("worker.js");
    // Ждем сообщений от worker
    worker.onmessage = function(e){
    // Сообщение от клиента:
    e.data;
    };
    worker.postMessage("start");
    Клиент:
    onmessage = function(e){
    if ( e.data === "start" ) {
    // Выполняем какие-нибудь вычисления
    done();
    }
    };
    function done(){
    // Отправляем полученный результат на главную страницу
    postMessage("done");
    }

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

    Прямо сейчас Web Workers присутствуют в Firefox 3.5 и Safari 4. Они также заявлены в последних ночных сборках Chromium (http://build.chromium.org/buildbot/snapshots/chromium-rel-xp/ ). Наверное, большинство тут же подумают: что нам до возможности, которая доступна лишь для небольшой части веб-пользователей (только в двух современных браузерах!), — но это не должно быть проблемой. Web Workers позволяют эффективно использовать пользовательские машины для параллельных вычислений. В такой ситуации вы можете создавать две версии своих приложений (одну для старых браузеров, и одну — для запуска через механизм Web Workers при его наличии в браузерах). В новых браузерах это просто будет работать значительно быстрее.

    Приведем некоторые занимательные примеры использования данного механизма, которые используют заявленное API.

    6.7.2. Расчет освещения (RayTracing)

    (рис 6.14) Расчет освещения при помощи Web Workers, источник ejohn.org

    Здесь Canvas применен для отрисовки рассчитанной сцены. Если включить Web Workers, то заметно, как картинка отрисовывается по частям. Это происходит благодаря разбиению всей работы на части и поручению каждого набора пикселей отдельному Worker. Этот Worker затем подает массив цветов для отрисовки на Canvas, а родительская страница их применяет. (Заметьте, сам по себе Worker ничего не изменяет.)

    6.7.3. Отслеживание движения

    (рис 6.15) Отслеживание движения при помощи Web Workers, источник: ejonh.org

    В этом случае применяется несколько технологий: элемент video, элемент canvas и отрисовка кадров видео на сам холст. Все отслеживание движения производится в фоновом режиме при помощи Web Workers (по- этому передача видео не останавливается и не блокируется).

    6.7.4. Эмуляция огня

    (рис 6.16) Эмуляция огня при помощи Web Workers, источник: ejohn.org

    Этот пример пытается ограничить несколько случайных точек, используя алгоритм эмуляции огня (simulated annealing). Также здесь приведено анимированное PNG-изображение (работает в Firefox 3.5), которое вращается, пока идет вычисление в фоновом режиме.

    6.7.5. Вычисление при помощи JavaScript Web Workers

    Недавно закончилось интересное соревнование от Engine Yard (http://www.engineyard.com/blog/2009/programming-contest-win-iphone-3gs-2k-cloud-credit/ ). Задание было следующим: необходимо было подобрать фразу, которая бы давала наиболее близкий к исходному SHA1LU, расстояние между хэшами вычислялось по Хэммингу (число отличающихся битов).

    Наибольший интерес представляли два участника, которые попытались решить эту задачу при помощи JavaScript. Их программы запускались в браузере и использовали пользовательский компьютер для проведения вычислений. Скорее всего, никто из них так и не добился впечатляющего результата (если брать во внимание тех кластерных монстров, с которыми пришлось соревноваться), но сам подход вызывает неподдельный интерес.

    В обоих случаях (если верить исходному коду) используется почти одна и та же тактика: рассчитываются несколько результатов, разделяемых по таймеру. Скорость подбора варьируется от браузера к браузеру на уровне 1000-1500 расчетов в секунду. Естественно, эти алгоритмы весьма ресурсоемки и полностью "убивают" пользовательский процессор и "замораживают" интерфейс браузера.

    По-видимому, это является великолепной возможностью применить Web Workers!

    Если взять реализацию Ray C Morgan (http://www.raycmorgan.com/), вырезать весь интерфейс и таймеры и разложить вычисления на 4 параллельных потока для Web Workers, то можно добиться скорости в 4500-9500 расчетов в минуту (в новых браузерах, которые поддерживают механизм Web Workers).

    По этим ссылкам можно посмотреть на демонстрацию (автором которой является Джон Ресиг) и скачать исходные коды:

  • Пример Web Worker для взлома SHA1 (http://ejohn.org/apps/webworkers/)
  • Исходный код для "родителя" (http://ejohn.org/apps/web-workers/run.js)
  • Исходный код для Worker (http://ejohn.org/apps/webworkers/worker.js)
  • 6.8. Клиентские хранилища

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

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

    6.8.1. Cookie

    Первопроходцем на ниве сохранения данных на клиенте можно назвать компанию Netscape, сотрудник которой придумал cookie — сохраняемые на компьютере пользователя в виде "ключ-значение" данные небольшого объема. Объем действительно небольшой, различные браузеры имеют разные ограничения, но даже в лучшем случае их не может быть больше 50, размером не более 4 Кб каждая. Некоторые версии Internet ExpLorer разрешают устанавливать не более 20 cookie, общим объемом не более 4 Кб (в IE 8 — 50 значений, общим объемом 10 Кб).

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

    Например, у популярного веб-сервера Apache есть ограничение на длину каждой строки запроса (директива LimitRequestLine), по умолчанию оно составляет 8 Кб. Отсюда следует, что ограничение сверху на размер cookie (в том худшем случае, если cookie целиком будет состоять из URL-encoded символов) — 2,6 Кб. Конечно, если вы имеете доступ к настройке вашего сервера, это ограничение можно снять.

    Но у cookie есть важное достоинство — к ним можно осуществлять доступ из клиентских скриптовых языков.

    API cookie, если это так можно назвать, очень примитивное. Поскольку использование cookie в браузере можно запрещать, приложению сначала желательно проверить значение свойства navigator.cookieEnabled. Если его значение — истина, то можно прочитать значение document.cookie.

    Значение этого свойства представляет собой строку, где записаны все cookie, объединенные через точку с запятой и пробел. Ключ и значение каждого cookie объединены через знак "равно", впрочем, из этого правила бывают исключения: авторам известны случаи, когда Internet Explorer записывал cookie с пустым значением без "равно".

    У cookie помимо имени есть несколько атрибутов (перечислены под- держиваемые всеми браузерами):

  • expires — время хранения cookie; если этот атрибут не указан, то cookie удалится после закрытия окна браузера, где значение этого cookie было создано. Чтобы удалить cookie, нужно поставить в это поле любую дату прошлого.
  • domain — домен, для которого создается данное значение, остальные домены могут иметь собственные cookie с тем же именем; если параметр не задан, используется текущее имя домена. Допустимые значения — домен, с которого загружен документ, или поддомен этого домена.
  • path — путь, для которого создается значение; по умолчанию используется путь текущей страницы. Путь тут трактуется как директория — значение будет существовать и для всех вложенных путей.
  • secure — флаг, наличие которого означает, что данное значение cookie будет передаваться только по HTTPS
  • Для того чтобы создать новое значение cookie из скриптового клиентского языка, нужно записать в document.cookie строку вида

    key=value; expires=Fri, 31-Dec-2010 23:59:59 GMT; path=/;
    domain=.example.net

    Как было сказано выше, все поля, кроме пары "ключ-значение", можно опустить. Точка перед доменом в параметре domain означает, что это значение будет установлено и для всех доменов выше уровнем. При записи нового значения cookie его значение в указанном домене изменится.

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

    6.8.2. userData behavior

    Компания Microsoft, которая первой начала осваивать веб-приложения в том смысле, в котором мы сейчас используем этот термин, довольно давно предложила программистам механизм для сохранения более существенных объемов данных на компьютере пользователя.

    Уже в Internet ExpLorer 5 появился так называемый userData behavior, собственное расширение Microsoft, которое позволяло хранить до 128 Кб данных в одной записи, общим объемом до 1 Мб, причем в интранете лимит еще более отодвигался — 512 Кб и 10 Мб.

    API по нынешним временам нельзя назвать удобным, но тогда это было частью интересной идеологии behavior, позволяющей разделить данные и логику.

    Данные хранятся в DOM-элементе, у которого, посредством CSS, указан behavior userData:

    function IEStorage (storagename) {
      this.storagename = storagename
        var el = document.createElement('div');
        el.setAttribute('id', 'ourstore-' + storagename);
        el.style.display = 'none';
        el.addBehavior('#default#userData');
        document.body.appendChild(el);
        this.storage = el;
        this.get = function (name) {
          this.storage.load(this.storagename);
          return this.storage.getAttribute(name);
    }
    this.set = function (name, value) {
      this.storage.setAttribute(name, value);
      this.storage.save(this.storagename)
    }
    this.del = function (name) {
      this.storage.removeAttribute(name);
      this.storage.save(this.storagename);
    }
    }

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

    Особенность этого хранилища — можно установить дату устаревания всех его элементов, удалены они будут при вызове load:

    var time = new Date();
    time.setHours(time.getHours() + 1);
    // через час данные будут удалены
    this.storage.expires = time.toUTCString();

    6.8.3. Flash Local Shared Object

    В марте 2002 года появилась шестая версия Flash, популярнейшего плагина для браузеров, установленного, по статистике, на 95% компьютеров. В этой версии появилось собственное хранилище — Local Shared Object, позволяющее хранить до 100 Кб данных без ведома пользователя и любой объем сверх этого с его разрешения. У роликов с одного домена единый Local Shared Object.

    Способ использования этого типа хранилища — установка на странице Flash-ролика, который будет обмениваться данными со скриптовым языком. Вплоть до восьмой версии Flash не существовало хорошего способа обмена данными со скриптовыми языками браузера, пока в Flash8 не появился ExternalInterface, реализованный, впрочем, с существенными ошибками.

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

    6.8.4. WHATWG DB Backend (openDatabase)

    Рабочая группа WHATWG была организована производителями браузеров Apple, Mozilla Foundation и Opera Software ASA. Именно эта группа создала документ Web Application 1.0, который лег в основу пятой версии HTML.

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

    Некоторые версии браузеров, основанных на WebKit — Safari (в том числе под iPhone) и Chromium также содержат openDatabase (именно так называется эта часть спецификации) и представляют собой несложный доступ к реляционной СУБД SQLite из JavaScript. Кроме того, поддержка openDatabase появилась в "Опере" 10.50.

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

    Упрощенный (с минимальной обработкой ошибок) класс для доступа к openDatabase выглядит так:

    function SafariStorage(name, maxsize) {
      this.db = null;
      if (!window.openDatabase) return;
      try {
        this.db = openDatabase(name, '1.0 ', 'Storage for ' + 
        name, maxsize); // maxsize — в байтах
      } catch (e) {
        this.db = null;
        return;
    }
    this.db.transaction(function (t) {
      t.executeSql('CREATE TABLE IF NOT EXISTS storage(k TEXT 
      UNIQUE NOT NULL PRIMARY KEY, v TEXT NOT NULL);', []);
    })
      this.get = function (name, fn, scope) {
        scope = scope || this;
        this.db.transaction(function (t) {
          t.executeSql('SELECT v FROM storage WHERE k = ? '
          [name], function (t, r) {
            if (r.rows.length) {
              fn.call(scope, r.rows.item(0)['v'])
            } else {
              fn.call(scope, null)
            }
          });
      });
    
    }
    this.set = function (name, value) {
      this.db.transaction(function (t) {
        t.executeSql('INSERT OR REPLACE INTO storage(k, v) 
        VALUES (?, ?)', [name, value]);
      });
    }
    this.del = function (name) {
      this.db.transaction(function (t) {
        t.executeSql('DELETE FROM storage WHERE k = ?', 
        [name]);
        });
      }
    }
    }

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

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

    6.8.5. globalStorage и localStorage

    Спецификация на globalStorage присутствует в ранних версиях черновика HTML5, откуда ее удалили из соображений безопасности. Основная идея globalStorage — дать разработчикам возможность обращаться к данным поддоменов, как это сделано в cookie.

    Этот вид хранилища был реализован в Firefox 2 (и в Internet Explorer 8.0 beta 1), но уже в следующей версии браузера возможность обращаться к данным поддоменов отключили. В такой "урезанной" версии globalStorage по возможностям эквивалентно хранилищу localStorage, которое заняло в HTML5 нишу своего небезопасного предшественника.

    В настоящий момент localStorage поддерживается браузерами FireFox 3.5 и выше, Internet Explorer 8.0 (с версии beta 2), Safari 4.0 и выше, а так же Opera 10.50 и выше.

    Ограничения, накладываемые на размер хранилища, устанавливаются разными производителями браузеров по-разному, так, в Firefox это 5 Мб, а в IE 8 — 10 миллионов байт (причем, учитывается место, занятое не только значениями, но и именами ключей). Другое ограничение накладывается на имена ключей, в частности, в них не может быть пробелов.

    function HTML5Storage() {
      if (window.localStorage) {
        this.storage = window.localStorage } else if (window.globalStorage) {
      this.storage = window.globalStorage[location.hostname J ||  'localhost.localdomain '] }   else {
        return false
      }
        this.get = function (name) {
        var out = this.storage.getItem(name); return out  out.value ? out.value : out;
      }
        this.set = function (name, value) { this.storage.setItem(name, value);
      }
        this.del = function (name) {
        this.storage.removeItem(name)
      }
    }

    6.8.6. Google Gears

    Последнее из рассматриваемых нами хранилищ — GoogLe Gears, расширение для браузеров, придуманное GoogLe. В настоящее время встроено в браузер GoogLe Chrome, для других поддерживаемых браузеров скачивается и устанавливается отдельно. Диапазон поддерживаемых браузеров довольно широк: Firefox начиная с версии 1.5, Internet ExpLorer 6.0 и выше, Safari 3.1.1 и выше (для Mac OS), а также мобильные браузеры — IE MobiLe с версии 4.01 и Opera MobiLe 9.51 и выше.

    Основная идея хранилища GoogLe Gears та же, что и у openDatabase, тот же самый доступ к SQLite, но с несколько другим API:

    function GearsStorage(name) {
      if (Iwindow.google || Iwindow.google.gears) { var factory = null; // Firefox
          if (typeof GearsFactory I= 'undefined') {
            factory = new GearsFactory(); } else { // IE try {
          factory = new ActiveXObject('Gears.Factory'); if (factory.getBuildInfo().indexOf('ie_mobile') I= -1) {
        factory.privateSetGlobalObject(this);
      }
    } catch (e) { // Safari
        if ((typeof navigator.mimeTypes I= 'undefined')  navigator.mimeTypes["application/ J x-googlegears"]) 
        {
            factory = document.createElement("object"); factory.style.display = "none"; factory.width = 0; factory.height = 0;
            factory.type = "application/x-googlegears"; document.documentElement.appendChild(factory);
        }
            }
              if (Ifactory) return; if (Iwindow.google) { google = {};
            }
          if (Igoogle.gears) {
              google.gears = {factory: factory};
            }
          }
            this._begin = function () {
            this.db.execute('BEGIN').close();
            }
            this._commit = function () {
            this.db. execute('COMMIT').close();
    }

    Как видно, существенную часть занимает инициализация Google Gears, в каждом браузере она выполняется по-разному. API устроено проще, чем в openDatabase — транзакции нужно создавать самостоятельно, зато есть возможность возвращать данные непосредственно, не прибегая к callback.

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

    6.8.7. Библиотеки для работы с клиентскими хранилищами

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

    PersistJS (http://pablotron.org/?cid=1557 ) — пожалуй, наиболее известная библиотека на этом поприще. Она поддерживает все перечисленные виды хранилищ (правда, Google Gears для IE не поддерживается), но, несмотря на свою известность, обладает рядом существенных недостатков.

    Первый недостаток — нет возможности изменить последовательность, в которой PersistJS перебирает методики хранения данных. К примеру, если вы решили поставить localStorage выше Google Gears, а cookie исключить, вам придется вмешиваться в код библиотеки.

    Другая проблема — если библиотека обнаружила, например, хранилище Google Gears, а клиент ответил отказом на запрос разрешения хранения данных, то PersistJS останется в неопределенном положении.

    Еще недоработка — PersistJS не проверяет готовность Flash-ролика, и в том случае, если вы используете это хранилище, есть небольшая вероятность того, что ваш код попытается обратиться к данным еще до того, как ролик будет загружен. Другая связанная с Flash-роликом проблема — библиотека не умеет запрашивать дополнительное место для хранения данных, если 100 Кб, которые можно использовать без запроса разрешения, исчерпаны.

    Неприятно также, что библиотека позволяет сохранять только текстовые строки, не предоставляя возможности сериализации, впрочем, версия из репозитория умеет представлять сложные объекты в виде JSON.

    Впрочем, у библиотеки хороший плюс — небольшой (около 9 Кб) размер.

    Dojo (http://www.dojotoolkit.org/ ) поддерживает хранилища Flash, Google Gears, globalStorage (Firefox 2.0) и среду запуска веб-приложений Adobe AIR.

    Этот фреймворк лишен недостатков PersistJS, но есть изъян — гигантский размер: версия 1.3.2 занимает 45 Мб. Конечно, для работы с хранилищем весь фреймворк не нужен, но даже минимально необходимый набор занимает более 100 Кб.

    Существует адаптированная версия Dojo Storage, которая называется SRAX Storage (http://fullajax.ru/#:download/), но она поддерживает только Flash Local Shared Object.

    jStore (http://code.google.com/p/jquery-jstore/) — небольшой плагин к jQuery, который поддерживает все рассмотренные хранилища, кроме cookie, обладает умеренным размером (около 15 Кб в минимизированном виде) и свободен от недостатков PersistJS.

    6.8.8. Резюме

    К сожалению, даже создатели HTML5 не предусмотрели возможности адресации к хранилищу посредством URL. Было бы очень удобно, положив в хранилище JavaScript или графическое изображение, подключить содержимое через обычный тег HTML 1 .

    Необходимо помнить, что хранилище — клиентское, и данные, сохраненные в хранилище, не будут доступны клиенту на другой машине и даже (увы) в другом браузере. Так что наиболее подходящее использование клиентских хранилищ — кэширование текстовых данных: JavaScript, CSS,

    Хранилище Ограничение Браузеры
    userData behavior Интранет — 512 Кб каждая запись, 10 Мб на домен, ограниченные узлы — 64 Кб / 640 Кб, остальные — 128 Кб / 1Мб IE 5.0+
    gLobaLStorage 5 Мб FF 2.0+, IE 8 beta 1
    LocaLStorage Firefox, Safari, Opera — 5 Мб Internet ExpLorer — 10 MiB FF 3.5+, Safari 4+, IE 8 beta 2+, Opera 10.50+
    openDatabase 5 Мб Opera 10.50+, некоторые версии Safari 3.xx, 4.xx, GoogLe Chrome 3.xx, 4.xx
    GoogLe Gears Запрашивает разрешение на использование, ограничений по размеру нет Встроен в GoogLe Chrome, плагины для IE 6+, Opera MobiLe 9.51+, FF 1.5+, IE MobiLe 4.01+, Safari 3.1.1+
    FLash Запрашивает разрешение на хранение более 100 Кб данных, ограничений по размеру нет Плагины для IE, FF, GoogLe Chrome, Safari

    1 - создатели GoogLe Gears озаботились доступом к бинарным данным из своего хранилища, изображениями можно манипулировать и выводить в окно браузера при помощи специального расширения, используя тег Canvas. а значит, данных о состоянии приложения и его редко изменяемых ресурсов, которые выгоднее хранить на стороне пользователя. Принцип использования прост: проверяется наличие нужного ресурса в хранилище; если ресурс обнаружен, он загружается из хранилища, если нет, с сервера (например, при помощи AJAX) и сохраняется в хранилище. Далее соответствующий тег с полученным содержимым создается в DOM. К сожалению, браузер Opera не предоставляет никакого встроенного хранилища, хотя в нем можно использовать Flash Local Shared Object; зато современные версии остальных браузеров не нуждаются в установке каких-либо дополнительных плагинов. Всеми браузерами без исключения поддерживаются cookie, но их вряд ли можно рекомендовать в качестве клиентского хранилища из-за сильных ограничений на размер и увеличения исходящего трафика. Из всех рассмотренных библиотек для доступа к хранилищам оптимальнее, на наш взгляд, использовать библиотеку jStore: она достаточно гибкая, небольшого размера и поддерживает все основные виды хранилищ.

    Вернуться к учебному плану