Данный раздел написан после прочтения ряда заметок Джона Реси-га (автора JavaScript-библиотеки
После существенной оптимизации CSS-селекторов и выхода SizzLe (http://sizzLejs.com/), который лишь немного уступает YASS (http://yass.webo.in/), автор
Также было написано дополнение для глубокого профилирования
(http://ejohn.org/blog/deep-profiling-jquery-apps/)
Для ответа на этот вопрос можно пойти по известному пути и замерить число вызовов функции из какого-либо метода. В этом нам может
помочь
Джон Ресиг немного улучшил описанную ситуацию и добавил пару новых методов в 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");
});
Во-первых, обязательно нужно установить последнюю версию (http://github.com/jeresig/fireunit ) FireUnit. Также можно скачать последнюю версию кода в виде расширения к Firefox: http://fireunit.org/fireunit-1.0a1.xpi
При запуске нужно будет убедиться, что:
Ниже приведены результаты вызовов из
| Метод | Вызовы | 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), большинство методов addClass запускает около шести
функций на каждый элемент, filter — примерно две, а is — только одну.
Также мы легко видим проблемные методы, напоминающие большие
черные дыры, в которые утекает процессорное время — .remove(), .empty() и .html(). Все они имеют сложности с вызовом функций n 2
, что является значительной проблемой для производительности ваших сайтов. Все эти числа выросли по очень простой причине: .html()
использует .empty(), .empty() использует .remove(), а .remove() работает крайне неэффективно. Если не начать профилировать вызовы функций на
медленное выполнение (к слову сказать, большинство внутренних методов
После внесения необходимых изменений мы придем к значительно улучшенным числам:
| Метод | Вызовы | O(n) |
|---|---|---|
| .remove(); | 298 | 3n |
| .html("<p>test</p>"); | 507 | 5n |
| .empty(); | 200 | 2n |
Автоматизированный процесс профилирования кода открывает широкие просторы для исследовательской деятельности. Даже не используя
ничего другого, кроме вышеописанного метода, уже можно значительно
улучшить каждый метод
Также весьма интересно интегрировать описанное профилирование как часть самого процесса разработки — чтобы сразу замечать очевидные недочеты в производительности.
Позиция John относительно философии развития
Данный раздел написан на основе статьи Christian Stockwell, отвечающего за производительность браузера IE. Измерение общей производительности веб-сайтов и браузеров крайне важно для большого числа людей: как для пользователей, использующих различные продукты, так и для разработчиков, оптимизирующих свои порталы. Также сами разработчики других браузеров внимательно следят за успехами их коллег по цеху и стараются ориентироваться на некоторые отраслевые стандарты качества.
Обычно для измерения производительности браузера используются специальные тесты-симуляции. И хотя они могут быть очень полезны во всех случаях, было бы ошибкой целиком полагаться на небольшое количество таких тестов, если мы хотим оценить производительность браузера в том смысле, в каком этот термин понимают обыкновенные пользователи.
Наилучшие способы измерения производительности браузера должны непременно содержать сценарии, отражающие реальные ситуации, в которых он используется. Работа с реальными веб-сайтами позволяет учесть те факторы, которые в случае применения тестов, симулирующих какую-то ситуацию, учету не поддаются, и передать целостное впечатление о производительности. Однако тестирование браузеров на настоящих веб-ресурсах связано с рядом нюансов, и в этом разделе обсуждаются некоторые способы, которые применяются для адекватного измерения производительности IE.
Прежде чем углубиться в детали, стоит заметить, что измерение производительности — весьма нетривиальная задача, как ни странно это звучит. Команда программистов IE положила немало трудов, создавая лабораторию тестов и производительности, в которой сотни ПК и ноутбуков ежедневно прокручивали тысячи отдельных тестов, обращаясь к огромному количеству серверов во Всемирной Паутине, и редкий день завершался без того, чтобы родилось несколько новых идей, как добиться точности, ясности и достоверности оценочных данных.
Часть проблем оценки производительности вызвана огромным количеством разнообразных действий, для которых используется браузер. Каждый день пользователи обращаются к широкому диапазону ресурсов — от насыщенного мультимедийным содержимым FLickr до спартанского GoogLe. Они могут столкнуться с интерактивным, насыщенным AJAX-скриптами сайтом, как Windows Live HotmaiL, или сайтом, содержащим лишь статический HTML, как, например, CraigsList, а некоторые из них станут использовать браузер для критически важных деловых приложений (например, построенных на его основе систем электронного документооборота).
Производительность каждого из этих ресурсов часто зависит от производительности отдельной подсистемы браузера. Например, загрузка насыщенного изображениями сайта может зависеть от скорости, с какой браузер в состоянии загружать и распаковывать изображения. Напротив, производительность простенькой страницы зависит от того, как быстро браузер обрабатывает стандартный HTML. В следующем случае для хорошей производительности насыщенного AJAX-скриптами портала потребуется тесная интеграция JavaScript, CSS и DOM, — и это окажется в большей степени важным, нежели индивидуальная производительность каждого из названных компонентов. Когда на чашу весов кладутся FLash и SiLverLight, производительность будет зависеть от того, насколько хорошо встроены в браузер соответствующие подсистемы управления.
Очевидно, что некоторые обсуждаемые тут подходы послужат лучшему представлению о той работе, которая была проделана для улучшения производительности IE 8, и позволят глубже заглянуть за кулисы процесса разработки браузеров. Изложенный ниже текст должен помочь по-новому подойти к процедурам оценки результатов измерения производительности и еще раз задуматься о том, что такое производительность браузера, а что такое производительность веб-сайта.
Все браузеры по сути своей работы зависят от характеристик сети, поэтому любые тесты должны отражать эту реальность — только тогда можно будет адекватно оценивать производительность.
Один из аспектов развития структуры Всемирной сети, который влияет на производительность браузера, — организация процесса сохранения часто запрашиваемого контента на узлах сети. Этот процесс называется кэшированием.
Что это означает в случае оценки производительности браузера? Например, при обращении к ресурсу http://www.microsoft.com ваш браузер может последовательно запрашивать данные из нескольких источников — с прокси-сервера вашей локальной сети, с сервера, расположенного к вам ближе всего, или с нескольких географически удаленных серверов.
Для повышения скорости загрузки содержимого страниц и распределения нагрузки по сети эти серверы могут сохранять часть загружаемых вами данных у себя в памяти, чтобы остальные пользователи могли быстрее получать к ним доступ. Например, утром, придя на работу, вы первым делом просматриваете новости на http://www.msnbc.com. Скорее всего, браузер попытается сначала загрузить запрашиваемую страницу с прокси-сервера, затем с ближайшего к вам сервера корпоративной сети — перед тем как обратиться к прочим, удаленным от вас ресурсам. Как только страница загрузится, ваш рабочий прокси-сервер или сервер в локальной сети может "решить" (разумеется, в зависимости от предварительно сделанных настроек) сохранить часть ее содержимого. Когда другой пользователь, спустя десять минут, попробует обратиться по тому же адресу, его компьютер сначала получит порцию данных, уже сохраненных на прокси-сервере, вместо их повторной загрузки с удаленных серверов, что, в свою очередь, значительно уменьшит время загрузки страницы и приведет пользователя в прекрасное расположение духа.
Довольно сложно тщательно проконтролировать, как серверы кэширу-ют данные, но одним из главных принципов при оценке производительности является условие никогда не измерять параметр один-единственный раз. Если вы не ставите задачи определить именно эффективность кэширования, то следует предварительно хотя бы раз загрузить страницы, производительность работы с которыми вы стремитесь оценить. Собственно, с тех пор, как прокси-серверы научились сохранять кэш для каждого из используемых браузеров, необходимо открывать отобранные для оценки производительности страницы в каждом из предназначенных для тестирования браузеров.
Принципы работы системы кэширования изложены здесь очень примитивно. Если требуется детальная информация по этому вопросу, то стоит обратиться к соответствующим ресурсам, включая, собственно, принципы работы НТТР-протокола.
Именно потому, что так много внешних факторов могут повлиять на оценку производительности, решающее значение имеет то, какие параметры и в каком количестве вы собираетесь учитывать.
Основной принцип оценки производительности — не измерять какой-либо параметр лишь однажды. Его стоит расширить до "всегда измеряйте нужный параметр достаточное количество раз".
Существует множество способов определить это самое "достаточное количество раз" — например, используя доверительные интервалы,
Когда данные собраны, необходимо проанализировать их, чтобы сделать выводы. Используете ли вы варианты среднего арифметического, гармонического, геометрического или какие-то иные методики, необходимо быть последовательными и полностью представлять себе схему ветвления результатов при подведении итогов тестирования.
Например, давайте посмотрим на таблицу пунктов, набранных двумя браузерами по итогам тестов навигации в пределах одной веб-страницы:
(рис 6.2) Рис. 6.2. Проблема выбора подходящего среднего, источник: blogs.msdn.comНа этом искусственном примере хорошо видно, что в зависимости от того, как подводятся итоги тестов, выводы о производительности будут противоположными: при выборе в качестве критерия среднего арифметического браузер А быстрее браузера В, а при выборе критериев среднего геометрического и гармонического — все наоборот.
Вы используете сеть не в одиночку, а наряду с другими пользователями, поэтому в какой-то момент ваш браузер вдруг без всяких видимых причин замедляет работу, и вы заметите, что на выполнение той же самой операции ему почему-то требуется гораздо больше времени.
При сборе данных в домашних условиях необходимо учесть, что и в этом случае вы делите канал с другими пользователями, например, членами семьи. В этом случае тесты лучше проводить тогда, когда в сети находится меньше пользователей — в рабочее время, поздно ночью или очень рано утром.
Совместное использование ресурсов приложениями на вашем компьютере также может повлиять на производительность браузера — по крайней мере так же сильно, как и совместное использование канала.
Это особенно заметно тогда, когда несколько программ зависят от внешних ресурсов или платформ. Например, некоторые антивирусы по-разному встраиваются в различные браузеры, разумеется, с не известной заранее степенью влияния на производительность.
Результаты тестирования двух браузеров одновременно, "бок о бок", могут оказаться совершенно некорректными. Например, платформа Windows имеет ограничение — возможны лишь 10 одновременных исходящих соединений; остальные запросы будут поставлены в очередь на выполнение по мере освобождения ресурсов и могут, в зависимости от необходимого временного интервала, завершиться успешно или с ошибкой. Такой способ тестирования означает, что вы, скорее всего, поставите один из браузеров в преимущественное положение тем, что запустите его на несколько микросекунд раньше соперника.
Можно рассмотреть всего два простых примера, хотя их существует множество. Не рекомендуется запускать другие приложения, когда вы тестируете производительность браузера. Вы должны предпринять следующие обязательные шаги для того, чтобы избежать влияния других программ:
%windir%\\system32\rundll32.exe advapi32.dll,ProcessIdleTasks
Кроме влияния совместного использования ресурсов и канала, на производительность может весьма значительно повлиять механизм работы серверов, к которым вы обращаетесь.
Одним из основополагающих принципов при проведении ваших тестов должно стать обеспечение равных условий на всех этапах и для всех аспектов тестирования. Для определения влияния процедур кэширования необходимо, чтобы серверы, к которым вы обращаетесь, накопили известное количество данных; для тестирования сети необходимо, чтобы среда выполнения тестов была изолирована от влияния внешних ресурсов.
Примером конструктивных особенностей приложения, способных оказать влияние на процесс тестирования, может служить программа управления банковским счетом. По соображениям безопасности такая программа получает доступ к данным только после того, как прошла авторизация пользователя. Цель тестирования состоит в сравнении поведения двух или более браузеров на веб-странице банка, содержащей такую программу. Для этого необходимо, чтобы программа находилась как бы в равных условиях по отношению к тестируемым браузерам.
Обычно такого рода программы не разрешают пользователям входить в систему одновременно из двух или более сессий: при повторной авторизации предыдущая сессия завершается (пользователь выходит из системы). Если предыдущее состояние веб-приложения не будет сброшено перед началом тестирования другого браузера, приложению может потребоваться дополнительное время для обработки повторного запроса, закрытия предыдущей сессии и запуска новой.
Все эти процедуры значительно влияют на процесс тестирования, и характерны не только для онлайновых банковских приложений, так что вам следует попытаться исключить их как фактор. В общем случае вы должны очень хорошо представлять себе особенности поведения тех веб-страниц, с помощью которых тестируете браузеры.
Во многих областях сам факт наблюдения изменяет характер поведения наблюдаемых объектов. Этот феномен получил наименование "эффекта наблюдателя".
Вы можете использовать любой набор библиотек для упрощения задачи тестирования некоторых сценариев использования браузера. Эти наборы обычно ориентированы на разработчиков и технически продвинутых пользователей. Как и в любом другом случае, когда результаты попадают в зависимость от способа измерения, необходимо тщательно оценить и по мере возможности ограничить колебания производительности, вызываемые типами библиотек, которые вы используете.
Время, необходимое для запуска браузера, может зависеть от многих факторов, и некоторые из них совершенно не относятся к качеству самой программы.
Как и в случае с кэшированием, время запуска браузера зависит от внешних условий, особенно если вы запускаете браузер первый раз. Прежде чем можно будет приступить к серфингу, необходимо, чтобы соответствующие модули браузера загрузились в оперативную память — процесс, требующий времени. Когда вы впервые загружаете браузер, трудно определить, какое количество необходимого кода уже находится в памяти. Особенно трудно это в случае с IE, поскольку многие его компоненты совместно используются другими приложениями.
Чтобы собрать наиболее непротиворечивые данные, откройте и закройте каждый браузер как минимум один раз перед тем, как начнете тестирование. Если все остальные приложения закрыты, это даст вашей операционной системе возможность загрузить нужные компоненты в память и обеспечит последовательность и точность результатов тестирования. Это также создаст равные условия конкуренции для разных браузеров, особенно в свете существования таких функций операционной системы, как Superfetch (http://www.microsoft.com/windows/windows-vista/features/superfetch.aspx), которая в ином случае обеспечит преимущества "любимому" браузеру.
Веб-сайты постоянно изменяются. К сожалению, это происходит и тогда, когда вам необходимо тестировать производительность браузера.
Можно на время тестирования закэшировать содержимое веб-страниц, чтобы гарантировать, что браузер всегда получает один и тот же контент для каждой итерации теста. В реальности такого, конечно же, не происходит.
Современные сайты обновляют содержимое очень часто. На
Вне лабораторного пространства контролировать такой характер изменений практически невозможно. Разумеется, существуют определенные подходы: например, вы можете использовать инструмент типа Fiddler для манипулирования контентом, который получает браузер. К сожалению, подобные методы с большой вероятностью могут привести к тому, что ожидаемого результата вы не достигнете. Решение состоит в том, чтобы следовать тем же рекомендациям, которые были даны выше относительно вопроса размера образцов для тестирования, и если вы обнаружите, что увесистый рекламный баннер появляется всякий раз после определенного количества загрузок страницы, то будет справедливо повторить измерения для обеспечения надежных результатов.
Изменение на веб-странице зависит не только от пользователя, но и от веб-мастера, который создал радикально отличающиеся версии своего ресурса для разных браузеров.
Один из подходов, используемых в том случае, когда необходимо обеспечить доставку одного и того же содержимого для ваших тестов — делать сайты, которые подстраиваются под тип браузера. В большинстве случаев следует игнорировать то, что для разных браузеров используется различный программный код. Это дает вам возможность реально оценить впечатления пользователей при посещении таких ресурсов.
Однако в некоторых случаях, в зависимости от того, каким браузером вы пользуетесь, изменяются и функциональные возможности страницы, причем настолько, что прямое сравнение браузеров становится невозможным. Существуют ресурсы, которые на самом деле предлагают различную функциональность в зависимости от типа браузера. Оценка производительности на базе таких сайтов — непростая задача, но стоит избегать прямого сравнения браузеров при их посещении, поскольку речь в этом случае идет более о взглядах и предпочтениях дизайнеров, нежели о производительности браузеров как таковых.
Определить, какие сайты ведут себя подобным образом, не всегда просто, и в этом случае веб-дизайнеры имеют преимущество перед "обычными" тестировщиками. Они должны использовать профайлеры, отладчики и другие имеющиеся в их распоряжении инструменты для того, чтобы определить участки контента, на которых разные браузеры ведут себя существенно отличающимся образом.
Рядовым тестировщикам и пользователям без глубоких технических познаний не следует оценивать производительность браузеров на примерах сайтов, слишком по-разному представляемых в разных браузерах, поскольку им сложно провести грань между производительностью сайта и особенностями его дизайна.
Можете ли вы точно определить, что означает "веб-страница загружена"? Как быть в случае, если она содержит сложные AJAX-сценарии?
Проблема при оценке производительности заключается в определении того, что, собственно, означает надпись "готово" в статусной строке браузера при загрузке страницы. А также в том, что некоторые страницы усложняются и разрастаются несогласованно друг с другом. Некоторые веб-программисты применяют маркер "загружено" (http://www.w3.org/ TR/htmL401/interact/scripts.htmL#h-18.2.3) как индикатор того, что браузер завершил разметку содержимого страницы для последующей загрузки. Этот маркер, к сожалению, интерпретируется разными браузерами по-разному.
Кроме индикаторов, работающих на уровне программного кода, некоторые используют, например, прогресс-бар браузера, текстовые поля и прочие общепринятые элементы графического интерфейса. Как правило, поведение этих элементов никак не регламентировано, и веб-программисты могут по своему усмотрению менять его, определяя, когда (если вообще!) отображать их на экране.
В таких ситуациях тестировщикам и пользователям
стоит применять те методы приближения, которые
основываются на индикаторе загрузки, с тем чтобы наблюдать поведение этого индикатора в процессе загрузки страниц.
Например, вы измеряете скорость первоначальной загрузки определенной страницы, и в то же время взаимодействуете с ее содержимым. Если страница
все еще как будто бы загружается, индикатор показывает "в прогрессе", но при этом вы можете взаимодействовать с контентом. Вы можете решить не обращать внимания на индикатор и
ориентироваться на визуальные параметры оценки, загрузилась страница до конца или нет. С другой стороны, индикатора загрузки может оказаться достаточно для предварительной оценки
скорости загрузки в различных браузерах. Однако если скорость, с какой страница загружается в действительности, не соответствует индикации в прогресс-баре, то будет довольно трудно понять,
до какой степени в этом случае можно доверять результатам
Использование надстроек означает, что с этого момента вы измеряете производительность не только самого браузера. Надстройки могут существенно влиять на производительность. Согласно данным, полученным из источников в Microsoft, IE используется в совокупности с дюжинами надстроек (относительно Mozilla ситуация абсолютно идентичная).
Любая из этих надстроек может проявлять произвольную активность внутри браузера. Иллюстрацией воздействия может служить следующая ситуация: пользователи, отстаивающие свои предпочтения в отношении определенного браузера, внезапно обнаруживают, что любая альтернативная программа работает быстрее лишь потому, что их любимый браузер перегружен надстройками и дополнениями, а альтернативный представляет собой чистую, без всякого "мусора" программу. Например, пользователь обремененного несколькими дополнениями Firefox может сменить его на IE, увидев, что тот работает быстрее, а в это время пользователь IE переходит на Firefox по той же схеме исходя из тех же причин. Здесь нет никакого противоречия — такие примеры лишь демонстрируют решающее влияние надстроек.
Для блокировки надстроек в IE 8 необходимо вызвать пункт Manage add-ons из меню Tools. В появившемся диалоговом окне выберите All Addons и последовательно заблокируйте все надстройки из списка. Если вы дружите с командной строкой, можете выполнить команду iexplore.exe -extoff для запуска IE без дополнений.
Поскольку большинство программистов предпочитает не сокращать объем кода, чтобы тем самым гарантировать ожидаемое поведение надстроек после обновлений, важно, особенно во время использования пробных версий, иметь возможность отключить любое из некорректно ведущих себя дополнений.
В этом разделе рассматривается часть прикладных методов, положенных в основу разработки YASS (http://yass.webo.in/) — самой быстрой библиотеки для выбора элементов по CSS-селекторам.
Начнем с наиболее очевидной составляющей любой логики — ветвления. В любом алгоритме встречается место, в котором нужно выбрать то или иное продолжение в зависимости от проверяемого условия. Давайте рассмотрим следующие простые примеры проверки. В первом случае у нас три простых вложенных проверки:
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, не
if (a b c) {
...
}
Очень часто нам нужно проверить что-то более сложное, чем просто число. Например, совпадение строки с заданной или равенство объектов. В этом случае нам просто необходимо следующее сравнение:
var a = 1,
b = 2,
c = '3';
if (a == 1 b == 2 c === '3') {
...
}
Здесь мы используем сравнение без приведения типов ===, которое в случае нечисловых переменных работает быстрее обычного сравнения на 10—20%.
Достаточно часто нам нужно выбрать одну из условных ветвей, основываясь на заданной строке. Обычно для этого используются либо методы объекта , либо
строковые методы ( match, search, indexOf ). Если нам нужно просто проверить соответствие строки какому-то регулярному выражению, то лучше всего для этого подойдет именно test:
var str = 'abc',
regexp = new RegExp('abc');
if (regexp.test(str)) {
...
}
Такая конструкция отработает на 40% быстрее, чем аналогичный exec:
if (regexp.exec(str)[1]) {
...
}
Строковый метод match аналогичен методу exec у создаваемого объекта
В том случае, если регулярное выражение требуется "на один раз", подойдет более быстрая (примерно на 10% относительно варианта с инициализацией нового объекта) запись:
if (/abc/.test(str)) {
...
}
Если же, наконец, нам нужно проверить просто нахождение подстроки в заданной строке, то тут бесспорным лидером будет именно indexOf, который работает в два раза быстрее разбора регулярных выражений:
if (str.indexOf('abc') != 1) {
...
}
Давайте теперь рассмотрим следующий вариант регулярного выражения: /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 {
...
}
switch (firstLetter) {
case '#':
...
break;
case '.':
...
break;
case ':'
...
break;
case '[':
...
break;
default:
...
break;
}
switch (ancestor) {
case ' ':
...
break;
case '∼':
...
break;
case '+':
...
break;
case '>':
...
break;
}
first-child, last-child, nth-child,
и т. д.) и
выбор проверочной функции для атрибутов ( $$\sim$$ =, *=, = и т. д.) осуществляется уже через специальные хэши:_.attr = {'': ... , '=': ... , '=': ... , '^=': ... ...}
Подводя небольшой итог для различных способов проверки строки, можно составить такую таблицу:
| Задача | Средство решения |
|---|---|
| Проверка числового значения | Обычное сравнение (==) |
| Проверка нескольких числовых значений | Сравнение их суммы |
| Проверка, что число не нуль, или проверка на существование | Проверка отрицания к заданной переменной (!) |
| Разбор строки и выделение частей в массив | String.match( или |
| Проверка строки на соответствие регулярному выражению | |
| Проверка строки на точное соответствие (либо соответствие одному из набора значений) | if без приведения типов (===) |
| Выбор в зависимости от точного значения (значений 1—2) | Условная конструкция if |
| Выбор в зависимости от точного значения (значений 3—8) | switch |
| Выбор в зависимости от точного значения (значений больше 8) | Хэш с ключами, соответствующими значениям |
Наверное, данную таблицу можно дополнить еще некоторыми случаями или же обратиться к статье, посвященной производительности простых конструкций в JavaScript
На данный момент насчитывается несколько десятков различных
Но в последнее время появилось несколько явных фаворитов на
этом поприще. Речь идет про Sizzle (движок выборки элементов, автором
которого является Джон Ресиг и который включен в
Поэтому возникает резонный вопрос, почему нельзя сделать быстрое мини-ядро для CSS-селекторов, которое обеспечит базовую функциональность для работы с DOM (например, совсем базовую — просто выборку элементов)? И, самое главное, чтобы это работало не медленнее (в идеале даже быстрее), чем вызовы в самом браузере.
Но описанная задача имеет решение. Можно создать библиотеку, которая будет осуществлять базовые операции практически так же быстро, как и сам браузер (а в некоторых случаях даже быстрее — за счет кэширования). И это удалось сделать в достаточно сжатые сроки. Далее речь пойдет о самой быстрой (на момент написания книги) библиотеки для выбора элементов по CSS-селекторам. Каковы же были причины для написания такой библиотеки?
Во-первых, такой код должен и кэшировать выборки (DOM-вызовы дорого обходятся, все нормальные JavaScript-программисты уже их кэши-руют — так упростим им задачу и повысим быстродействие).
Во-вторых, есть статистика использования CSS-селекторов в проектах — значит, у нас есть набор элементарных операций, которые должны выполняться быстрее всего (может быть, даже в ущерб более общей производительности).
В-третьих, библиотека должна возвращать сами элементы, чтобы к ним можно было "прикрутить" любые обертки и нарастить методы. Все обертки ресурсоемки. Если нужно просто 200 раз поменять HTML на странице у заданных элементов, то они не нужны. Достаточно и проверок со стороны браузера на допустимость выполняемых операций.
В-четвертых, естественно, библиотека должна опираться на все самое быстрое: быстрые итераторы, быстрые подстановки, анонимные функции, минимум вложенных вызовов и т. д.
Синтаксис такой библиотеки до безобразия прост:
_(‘p’) — вернет все параграфы на странице;_(‘p a’) — или все ссылки в них;_(‘p a.blog ’) — или все ссылки с классом blog .Может быть, кому-то данная разработка покажется тривиальной, но дальнейшее развитие ситуации видится следующим образом. Код YASS можно использовать в образовательных целях, чтобы понять логику конечных автоматов, используемых браузером для распознавания CSS-се-лекторов (во встроенном движке), и промоделировать некоторые тривиальные или не очень ситуации.
Или же данный код можно будет доработать и включать в основу высокопроизводительных библиотек, которые уже будут при его помощи ре-ализовывать свои методы (одной из таких библиотек,
с которыми YASS уже интегрирован, является js-core, http://code.googLe.eom/p/js-core/). Также возможна замена кодом YASS
встроенного механизма выборки элементов по CSS-селектору в таких распространенных библиотеках, как MooTooLs,
Но давайте рассмотрим процесс выборки CSS-селекторов более детально.
Начнем с самого простого: чего мы хотим добиться? Мы хотим, задав произвольную строку CSS-селектора, соответствующую спецификации (http://www.w3.org/TR/2005/WD-css3-seLectors-20051215/), получить на выходе массив из всех элементов, соответствующих этой самой строке. Вроде пока все просто.
В качестве иллюстрации спецификации можно привести следующие примеры (работает во всех современных браузерах и IE8+):
// вернет элемент с идентификатором my_id
querySelectorAll('#my_id')
// вернет все элементы с классом external
querySelectorAll('.external')
// вернет все абзацы на странице
querySelectorAll('p')
Однако уже тут можно отметить один момент: очень часто нам нужно
выбрать просто элемент по его идентификатору или найти все элементы с
определенным классом. Эти операции встречаются достаточно часто во
всех
Если посмотреть на современные JavaScript-библиотеки, то везде такая проверка уже осуществляется с помощью регулярного выражения. И тут сразу же небольшая хитрость: инициализация любого регулярного выражения обходится достаточно дорого в браузерах (хотя сложность выражения начинает влиять на затраченное время только при большой его длине), и разумно было бы вообще без него обойтись. И когда можно обойтись indexOf для проверки подстроки — этим (в элементарных случаях) всегда нужно пользоваться.
Может так случиться, что регулярное выражение обеспечивает корректность, и замена его никак не приведет к потере читаемости кода и непонятному выигрышу (или даже проигрышу) в скорости выполнения. Тогда можно заменить, например, exec на связку test и charAt / substr: это позволит увеличить производительность примерно на 20%. Если данный участок кода выполняется в цикле многократно, ускорение может оказаться достаточно существенным.
В YASS данная задача решена следующим образом:
// проверяем, удовлетворяет ли селектор простому случаю
if (/^[\w[:#.][\w\]*^|=!]*$/.test(selector)) {
// в случае положительного результата инициализируем переменную,
// которая отвечает за '#', '.' , ':' или '[' в начале селектора
var firstLetter = selector.charAt(0);
...
}
Но давайте рассмотрим, как решена общая задача по разбору CSS-селекторов. Если принять во внимание, что селектор может быть задан в
виде p a.link, form input[type=radio], то логику его разбора можно
схематично записать в следующем виде:?
sets ).p a.link ). Нам нужно разбить последовательность на
части и разобрать каждую такую часть, учитывая, что родительскими
элементами для следующей части будут выбранные элементы из
предыдущей. За "превращение" дочерних узлов в родительские
(прямо процесс взросления получается) отвечает массив nodes.single = regexp.exec(single); tag = single[1]; id = single[2]; ...
Как мы видим, основная логика данной задачи включает как мини-
мум одно регулярное выражение (использование indexOf и substring
будет при такой сложности намного более ресурсоемко) и 3 цикла (которые
нужно сделать максимально быстрыми). Не стоит перечислять все возможности быстрого выбора элементов, просто сделаем акцент на
некотоых аспектах.
Пусть у нас объявлен некоторый массив 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;
}
Именно такой код используется для сброса у элемента флага, отмечающего состояние "выбран". Давайте рассмотрим, зачем этот флаг нужен и зачем его сбрасывать.
Одной из основных проблем в ходе выбора элементов по 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).
YASS (http://yass.webo.in/) создавалась и продолжает разрабатываться для поиска и реализации наиболее производительных методов для решения определенного круга задач. Ее можно применять как в учебных целях, так и в чисто практических (например, для составления набора неиспользуемых на сайте CSS-селекторов — с помощью YASS это реализуется быстрее всего).
Данный раздел написан под впечатлением от статьи DOM
Предварительная версия документа API для селекторов (http://dev.w3.org/2006/webapi/selectors-api/), опубликованная консорциумом W3C, представляет собой относительно новый взгляд для JavaScript-разработчиков на то, как можно выбирать DOM-элементы на страницы при помощи CSS-селекторов. В одном этом документе собраны все тонкости такого сложного процесса, как поиск, выборка элементов из DOM-дерева и представление результата, доступного по упорядоченному интерфейсу.
Несмотря на все недавние войны по поводу интеграции стандартов в браузеры, этот является одним из наиболее поддерживаемых: его можно использовать прямо сегодня в браузерах Internet ExpLorer 8, Chrome и Safari, а также в Firefox 3.5 и Opera 10.
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 для селекторов весьма просто
(каждый принимает только один аргумент на вход), проблемы наступают
при выборе подходящей
Итак, самой большой проблемой для новых пользователей API для
селекторов является выбор корректной CSS-спецификации для использования. Особенно это актуально для большинства разработчиков, который
пишут кроссбраузерный код и поэтому ориентируются на
Изучение спецификаций
Наиболее часто встречающийся случай применения API для CSS-се-лекторов — это использование его не напрямую, а при помощи разнообразных сторонних библиотек, которые также обеспечивают функциональность CSS-селекторов для DOM. Сегодня основная проблема внедрения применения API для селекторов заключается в том, что они не поддерживаются во всех браузерах, для которых ведется разработка (в частности, это IE 6, IE 7 и Firefox 3). Поэтому пока эти браузеры еще не вышли из обращения, нам будут требоваться некоторые промежуточные утилиты для восстановления недостающей функциональности CSS-селекторов для DOM.
Однако, к счастью, на данный момент таких библиотек — огромное число, и все они поддерживают интерфейс выбора элементов, совместимый с API для селекторов API (на самом деле последнее возникло как раз из рассмотрения текущей ситуации с выбором элементов и предложением интеграции в браузеры некоторой часто требуемой функциональности). В дополнение к этому существует некоторое количество фреймворков, которые уже переключаются на API для селекторов при наличии его в браузере (поэтому вы можете совершенно спокойно использовать их и не думать о применении каких-либо более эффективных инструментов для ускорения клиентской части вашего сайта,). Это означает, что вы можете работать с CSS-селекторами прямо сегодня и получить все возможные преимущества от их повышенного быстродействия в некоторых браузерах за счет API для селекторов, и это обойдется вам совершенно бесплатно.
Некоторые из существующих фреймворков, использующих по возможности API для селекторов:
Стоит также подчеркнуть, что применение нового API влечет значительный выигрыш в производительности (по сравнению с обычными методами выбора элементов из DOM при помощи JavaScript).
Вы сможете самостоятельно убедиться в этом, просто судя по улучшению ситуации в
Согласно уже проведенным тестам результаты получаются примерно следующими:
(рис 6.3) Прирост в производительности после внедрения API для селекторов, источник: hacks.mozilla.orgНевооруженным взглядом виден прирост в производительности после внедрения использования нового API для селекторов — то же самое произойдет с вашими веб-приложениями, применяющими указанные фреймворки, в современных браузерах.
Чтобы сравнить определение спецификации API для селекторов (http://dev.w3.org/2006/webapi/seLectors-api/) с фактической реализацией, было создано специальное тестовое окружение (автор — Джон Ресиг из MoziLLa). Это тестовое окружение может быть в том числе использовано для проверки основных браузеров на уровень соответствия стандартам.
Текущие результаты для браузеров, которые поддерживают это API, следующие:
Internet Explorer 8, как уже было упомянуто ранее, не реализует логику CSS3-селекторов (наверное, в силу того, что спецификация еще не утверждена w3.org), поэтому проваливает большую часть тестов.
По всей видимости, API для селекторов должно обеспечить простой и быстрый путь для выборки DOM-элементов на странице. Это действительно здорово, что все JavaScript-библиотеки используют тот же самый синтаксис и обеспечивают ту же функциональность. Стоит постараться разобраться в этом сейчас и начать применять этот API.
Прежде чем начать разговор о текущем положении вещей с поддержкой векторной и скалярной графики в браузерах, стоит немного углубиться в историю.
В 1998 году компания Microsoft при поддержке других крупных производителей предложила W3C свой стандарт отображения векторной информации —
В этом же 1998 году Adobe, IBM, Netscape и Sun вносят в W3C предложение о рассмотрении своего стандарта в противовес Microsoft —
Сейчас большинство
<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>
Спецификация Canvas (как отдельной области на странице, внутри которой можно отображать графические объекты) в 2005 году изначально была предложена со стороны Apple для поддержки некоторых приложений внутри движка WebKit (на данный момент его используют браузеры Safari и Chrome). Рабочая группа W3C включила Canvas в Web Applications 1.0, который вошел в готовящийся стандарт HTML 5.0.
Сейчас встроенная поддержка Canvas реализована в том или ином виде во всех современных браузерах. В IE версии 8 и ниже она эмулируется при помощи отдельного
В качестве основного отличия от
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 (и, скажем,
Подробнее со спецификацией Canvas можно ознакомиться, например, на странице WHATWG (http://www.whatwg.org/specs/web-apps/ current-work/muLtipage/the-canvas-eLement.htmL).
Примеров использования Canvas довольно много, и они частично охватывают уже известные области 2D-графики.
2D-проекции 3D-объектов
С помощью Canvas можно изображать 2D-проекции трехмерных объектов. В силу того, что все преобразования выполняются на пиксельном уровне, достаточно просто создать необходимые интерфейсы для изометрических проекций трехмерных объектов. Видимо, уже скоро на основе Canvas появятся полноценные 3D-экскурсии в браузерах или онлайн-игры "от первого лица".
(рис 6.5) Изображение чайника при помощи Canvas, источник: www.nihilogic.dkTypeface.js
В качестве следующего применения стоит упомянуть библиотеку
Для применения библиотеки
Для больших порталов (где размер страницы составляет 500-1000 Кб) данный подход вполне приемлем (если "стилизованных" заголовков будет не так много, чтобы лишний раз не нагружать браузер преобразованиями страницы). Для небольших сайтов загрузка нескольких десятков Кб JavaScript-кода для стилизации 10 Кб HTML- и CSS-кода выглядит не очень уместной.
(рис 6.6) Применение Canvas для стилизации шрифтов, источник typeface.neocracy.orgCufo’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.comRaphaеl
Если предыдущий пример был посвящен использованию Canvas больше
в развлекательных целях, то библиотека Raphael.js (http://raphaeljs.com/)
преследует сугубо практические цели (хотя и делает это с помощью SVG +
Применение этой библиотеки предельно просто: обычно нужно объявить необходимые данные и задать один из множества доступных представлений (или создать свое собственное). Более подробно с данной библиотекой можно ознакомиться на ее официальном сайте —http://raphaeljs.com/.
На данный момент 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. Также будет необходимо дублировать
всю функциональность через
Данный раздел написан под впечатлением статьи Джона Ресига "Computing with JavaScript Web Workers" (http://ejohn.org/blog/ web-workers/), в которой он раскрыл особенности встроенного в браузеры механизма "отложенных" вычислений при помощи JavaScript и его будущие перспективы.
Последняя спецификация Web Workers, несомненно, является наиболее перспективной из грядущих нововведений в браузерах, которая позволяет исполнять JavaScript-задачи параллельно, не блокируя интерфейс браузера.
Обычно для того, чтобы добиться более-менее приемлемых вычислений при помощи JavaScript-движка, необходимо было разбивать всю работу на небольшие части и запускать их друг за другом при помощи таймера. Это как не самый быстрый, так и совершенно не эффективный путь достижения поставленной задачи.
Имея в виду текущее положение вещей, давайте углубимся в спецификацию Web Workers.
Рекомендация Web Worker (http://www.whatwg.org/specs/web-workers/ current-work/) частично основывается на
уже проделанной работе со стороны команды
Worker — это скрипт, который может быть загружен и исполнен в фоновом режиме. Web Workers позволяют легко это сделать, например:
В этом примере будет загружен скрипт, расположенный по адресу worker.js, а его выполнение будет произведено в фоновом режиме (скорее всего, браузеры будут использовать встроенные средства операционной системы — нити — для реализации такого поведения).
Однако есть несколько серьезных "подводных камней":
document,
getElementById и т. д. (Наиболее важными исключениями из
этого правила будут методы Имея в виду эти ограничения, стоит сразу задаться вопросом: а для чего, собственно, может пригодиться Worker и какие задачи он способен решать?
Вы можете использовать Worker, обмениваясь с ним сообщениями.
Все браузеры (которые поддерживают данную спецификацию)
позволяют обмениваться строковыми сообщениями (Firefox 3.5 также
поддерживает обмен
Обмен сообщениями производится при помощи 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.14) Расчет освещения при помощи Web Workers, источник ejohn.orgЗдесь Canvas применен для отрисовки рассчитанной сцены. Если включить Web Workers, то заметно, как картинка отрисовывается по частям. Это происходит благодаря разбиению всей работы на части и поручению каждого набора пикселей отдельному Worker. Этот Worker затем подает массив цветов для отрисовки на Canvas, а родительская страница их применяет. (Заметьте, сам по себе Worker ничего не изменяет.)
(рис 6.15) Отслеживание движения при помощи Web Workers, источник: ejonh.orgВ этом случае применяется несколько технологий: элемент video,
элемент canvas и отрисовка кадров видео на сам холст. Все отслеживание
движения производится в фоновом режиме при помощи Web Workers (по-
этому передача видео не останавливается и не блокируется).
(рис 6.16) Эмуляция огня при помощи Web Workers, источник: ejohn.orgЭтот пример пытается ограничить несколько случайных точек, используя алгоритм эмуляции огня (simulated annealing). Также здесь приведено анимированное PNG-изображение (работает в Firefox 3.5), которое вращается, пока идет вычисление в фоновом режиме.
Недавно закончилось интересное соревнование от 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).
По этим ссылкам можно посмотреть на демонстрацию (автором которой является Джон Ресиг) и скачать исходные коды:
Чем больше сайтов становятся веб-приложениями, тем более возрастает потребность хранить какие-то данные на компьютере пользователя. Чаще всего речь идет о кэшировании, особенно если приложение предоставляет возможность работать без доступа к Интернету.
Хранение на стороне пользователя данных — настроек приложения, его состояния, частей кода и прочего, — способно сильно разгрузить канал, увеличить скорость загрузки и улучшить время реакции приложения на действия пользователя.
Первопроходцем на ниве сохранения данных на клиенте можно назвать компанию 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 получила дальнейшее развитие за прошедшие годы, стандарт расширился, но поскольку управление этими расширениями из клиентских скриптовых языков недоступно, рассматривать их мы не будем.
Компания 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();
В марте 2002 года появилась шестая версия Flash, популярнейшего
плагина для браузеров, установленного, по статистике, на 95% компьютеров.
В этой версии появилось собственное хранилище — Local Shared
Object, позволяющее хранить до 100 Кб данных без ведома пользователя
и любой объем сверх этого с его разрешения. У роликов с одного домена
единый Local
Способ использования этого типа хранилища — установка на странице Flash-ролика, который будет обмениваться данными со скриптовым языком. Вплоть до восьмой версии Flash не существовало хорошего способа обмена данными со скриптовыми языками браузера, пока в Flash8 не появился ExternalInterface, реализованный, впрочем, с существенными ошибками.
Хотя и прежние способы, более похожие на хаки, позволяют производить обмен (по крайней мере теоретически), универсальные библиотеки для доступа к клиентским хранилищам, которые будут рассмотрены далее, требуют для своей работы восьмую версию Flash.
Рабочая группа WHATWG была
организована производителями
браузеров Apple, Mozilla Foundation
и Opera Software
В частности, этот документ решал и проблему хранения данных веб-приложения на стороне пользователя. Текущая спецификация 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 (обратите внимание на регистр букв!) также есть
опциональные аргументы: после текста запроса и его подстановок можно указать
Спецификация на globalStorage присутствует в ранних версиях черновика HTML5, откуда ее удалили из соображений безопасности. Основная идея globalStorage — дать разработчикам возможность
обращаться к данным
Этот вид хранилища был реализован в Firefox 2 (и в Internet Explorer 8.0 beta 1), но уже в следующей версии браузера возможность обращаться к данным
В настоящий момент 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)
}
}
Последнее из рассматриваемых нами хранилищ —
Основная идея хранилища
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();
}
Как видно, существенную часть занимает инициализация
Авторы не обладают сведениями об ограничениях на размер этого хранилища, вполне может быть, что никаких ограничений, помимо тех, что есть в самом SQLite, не существует.
Итак, мы рассмотрели основные из имеющихся на сегодняшний момент в распоряжении программиста клиентских хранилищ. Такое обилие решений, большинство из которых поддерживаются ограниченным набором браузеров, не могло не привести к появлению специализированных библиотек для работы с клиентскими хранилищами. Не умаляя полезности минимальных знаний о работе каждого хранилища, мы все же рекомендуем для доступа к ним использовать одну из готовых библиотек, которые будут рассмотрены ниже.
Первый недостаток — нет возможности изменить последовательность, в которой PersistJS перебирает методики хранения данных. К примеру, если вы решили поставить localStorage выше
Другая проблема — если библиотека обнаружила, например, хранилище
Еще недоработка — PersistJS не проверяет готовность Flash-ролика, и в том случае, если вы используете это хранилище, есть небольшая вероятность того, что ваш код попытается обратиться к данным еще до того, как ролик будет загружен. Другая связанная с Flash-роликом проблема — библиотека не умеет запрашивать дополнительное место для хранения данных, если 100 Кб, которые можно использовать без запроса разрешения, исчерпаны.
Неприятно также, что библиотека позволяет сохранять только текстовые строки, не предоставляя возможности сериализации, впрочем, версия из репозитория умеет представлять сложные объекты в виде
Впрочем, у библиотеки хороший плюс — небольшой (около 9 Кб) размер.
Этот фреймворк лишен недостатков PersistJS, но есть изъян — гигантский размер: версия 1.3.2 занимает 45 Мб. Конечно, для работы с хранилищем весь фреймворк не нужен, но даже минимально необходимый набор занимает более 100 Кб.
Существует адаптированная версия Dojo Storage, которая называется SRAX Storage (http://fullajax.ru/#:download/), но она поддерживает только Flash Local
К сожалению, даже создатели 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 |
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 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 - создатели
Данный раздел написан после прочтения ряда заметок Джона Реси-га (автора JavaScript-библиотеки
После существенной оптимизации CSS-селекторов и выхода SizzLe (http://sizzLejs.com/), который лишь немного уступает YASS (http://yass.webo.in/), автор
Также было написано дополнение для глубокого профилирования
(http://ejohn.org/blog/deep-profiling-jquery-apps/)
Для ответа на этот вопрос можно пойти по известному пути и замерить число вызовов функции из какого-либо метода. В этом нам может
помочь
Джон Ресиг немного улучшил описанную ситуацию и добавил пару новых методов в 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");
});
Во-первых, обязательно нужно установить последнюю версию (http://github.com/jeresig/fireunit ) FireUnit. Также можно скачать последнюю версию кода в виде расширения к Firefox: http://fireunit.org/fireunit-1.0a1.xpi
При запуске нужно будет убедиться, что:
Ниже приведены результаты вызовов из
| Метод | Вызовы | 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), большинство методов addClass запускает около шести
функций на каждый элемент, filter — примерно две, а is — только одну.
Также мы легко видим проблемные методы, напоминающие большие
черные дыры, в которые утекает процессорное время — .remove(), .empty() и .html(). Все они имеют сложности с вызовом функций n 2
, что является значительной проблемой для производительности ваших сайтов. Все эти числа выросли по очень простой причине: .html()
использует .empty(), .empty() использует .remove(), а .remove() работает крайне неэффективно. Если не начать профилировать вызовы функций на
медленное выполнение (к слову сказать, большинство внутренних методов
После внесения необходимых изменений мы придем к значительно улучшенным числам:
| Метод | Вызовы | O(n) |
|---|---|---|
| .remove(); | 298 | 3n |
| .html("<p>test</p>"); | 507 | 5n |
| .empty(); | 200 | 2n |
Автоматизированный процесс профилирования кода открывает широкие просторы для исследовательской деятельности. Даже не используя
ничего другого, кроме вышеописанного метода, уже можно значительно
улучшить каждый метод
Также весьма интересно интегрировать описанное профилирование как часть самого процесса разработки — чтобы сразу замечать очевидные недочеты в производительности.
Позиция John относительно философии развития
Данный раздел написан на основе статьи Christian Stockwell, отвечающего за производительность браузера IE. Измерение общей производительности веб-сайтов и браузеров крайне важно для большого числа людей: как для пользователей, использующих различные продукты, так и для разработчиков, оптимизирующих свои порталы. Также сами разработчики других браузеров внимательно следят за успехами их коллег по цеху и стараются ориентироваться на некоторые отраслевые стандарты качества.
Обычно для измерения производительности браузера используются специальные тесты-симуляции. И хотя они могут быть очень полезны во всех случаях, было бы ошибкой целиком полагаться на небольшое количество таких тестов, если мы хотим оценить производительность браузера в том смысле, в каком этот термин понимают обыкновенные пользователи.
Наилучшие способы измерения производительности браузера должны непременно содержать сценарии, отражающие реальные ситуации, в которых он используется. Работа с реальными веб-сайтами позволяет учесть те факторы, которые в случае применения тестов, симулирующих какую-то ситуацию, учету не поддаются, и передать целостное впечатление о производительности. Однако тестирование браузеров на настоящих веб-ресурсах связано с рядом нюансов, и в этом разделе обсуждаются некоторые способы, которые применяются для адекватного измерения производительности IE.
Прежде чем углубиться в детали, стоит заметить, что измерение производительности — весьма нетривиальная задача, как ни странно это звучит. Команда программистов IE положила немало трудов, создавая лабораторию тестов и производительности, в которой сотни ПК и ноутбуков ежедневно прокручивали тысячи отдельных тестов, обращаясь к огромному количеству серверов во Всемирной Паутине, и редкий день завершался без того, чтобы родилось несколько новых идей, как добиться точности, ясности и достоверности оценочных данных.
Часть проблем оценки производительности вызвана огромным количеством разнообразных действий, для которых используется браузер. Каждый день пользователи обращаются к широкому диапазону ресурсов — от насыщенного мультимедийным содержимым FLickr до спартанского GoogLe. Они могут столкнуться с интерактивным, насыщенным AJAX-скриптами сайтом, как Windows Live HotmaiL, или сайтом, содержащим лишь статический HTML, как, например, CraigsList, а некоторые из них станут использовать браузер для критически важных деловых приложений (например, построенных на его основе систем электронного документооборота).
Производительность каждого из этих ресурсов часто зависит от производительности отдельной подсистемы браузера. Например, загрузка насыщенного изображениями сайта может зависеть от скорости, с какой браузер в состоянии загружать и распаковывать изображения. Напротив, производительность простенькой страницы зависит от того, как быстро браузер обрабатывает стандартный HTML. В следующем случае для хорошей производительности насыщенного AJAX-скриптами портала потребуется тесная интеграция JavaScript, CSS и DOM, — и это окажется в большей степени важным, нежели индивидуальная производительность каждого из названных компонентов. Когда на чашу весов кладутся FLash и SiLverLight, производительность будет зависеть от того, насколько хорошо встроены в браузер соответствующие подсистемы управления.
Очевидно, что некоторые обсуждаемые тут подходы послужат лучшему представлению о той работе, которая была проделана для улучшения производительности IE 8, и позволят глубже заглянуть за кулисы процесса разработки браузеров. Изложенный ниже текст должен помочь по-новому подойти к процедурам оценки результатов измерения производительности и еще раз задуматься о том, что такое производительность браузера, а что такое производительность веб-сайта.
Все браузеры по сути своей работы зависят от характеристик сети, поэтому любые тесты должны отражать эту реальность — только тогда можно будет адекватно оценивать производительность.
Один из аспектов развития структуры Всемирной сети, который влияет на производительность браузера, — организация процесса сохранения часто запрашиваемого контента на узлах сети. Этот процесс называется кэшированием.
Что это означает в случае оценки производительности браузера? Например, при обращении к ресурсу http://www.microsoft.com ваш браузер может последовательно запрашивать данные из нескольких источников — с прокси-сервера вашей локальной сети, с сервера, расположенного к вам ближе всего, или с нескольких географически удаленных серверов.
Для повышения скорости загрузки содержимого страниц и распределения нагрузки по сети эти серверы могут сохранять часть загружаемых вами данных у себя в памяти, чтобы остальные пользователи могли быстрее получать к ним доступ. Например, утром, придя на работу, вы первым делом просматриваете новости на http://www.msnbc.com. Скорее всего, браузер попытается сначала загрузить запрашиваемую страницу с прокси-сервера, затем с ближайшего к вам сервера корпоративной сети — перед тем как обратиться к прочим, удаленным от вас ресурсам. Как только страница загрузится, ваш рабочий прокси-сервер или сервер в локальной сети может "решить" (разумеется, в зависимости от предварительно сделанных настроек) сохранить часть ее содержимого. Когда другой пользователь, спустя десять минут, попробует обратиться по тому же адресу, его компьютер сначала получит порцию данных, уже сохраненных на прокси-сервере, вместо их повторной загрузки с удаленных серверов, что, в свою очередь, значительно уменьшит время загрузки страницы и приведет пользователя в прекрасное расположение духа.
Довольно сложно тщательно проконтролировать, как серверы кэширу-ют данные, но одним из главных принципов при оценке производительности является условие никогда не измерять параметр один-единственный раз. Если вы не ставите задачи определить именно эффективность кэширования, то следует предварительно хотя бы раз загрузить страницы, производительность работы с которыми вы стремитесь оценить. Собственно, с тех пор, как прокси-серверы научились сохранять кэш для каждого из используемых браузеров, необходимо открывать отобранные для оценки производительности страницы в каждом из предназначенных для тестирования браузеров.
Принципы работы системы кэширования изложены здесь очень примитивно. Если требуется детальная информация по этому вопросу, то стоит обратиться к соответствующим ресурсам, включая, собственно, принципы работы НТТР-протокола.
Именно потому, что так много внешних факторов могут повлиять на оценку производительности, решающее значение имеет то, какие параметры и в каком количестве вы собираетесь учитывать.
Основной принцип оценки производительности — не измерять какой-либо параметр лишь однажды. Его стоит расширить до "всегда измеряйте нужный параметр достаточное количество раз".
Существует множество способов определить это самое "достаточное количество раз" — например, используя доверительные интервалы,
Когда данные собраны, необходимо проанализировать их, чтобы сделать выводы. Используете ли вы варианты среднего арифметического, гармонического, геометрического или какие-то иные методики, необходимо быть последовательными и полностью представлять себе схему ветвления результатов при подведении итогов тестирования.
Например, давайте посмотрим на таблицу пунктов, набранных двумя браузерами по итогам тестов навигации в пределах одной веб-страницы:
(рис 6.2) Рис. 6.2. Проблема выбора подходящего среднего, источник: blogs.msdn.comНа этом искусственном примере хорошо видно, что в зависимости от того, как подводятся итоги тестов, выводы о производительности будут противоположными: при выборе в качестве критерия среднего арифметического браузер А быстрее браузера В, а при выборе критериев среднего геометрического и гармонического — все наоборот.
Вы используете сеть не в одиночку, а наряду с другими пользователями, поэтому в какой-то момент ваш браузер вдруг без всяких видимых причин замедляет работу, и вы заметите, что на выполнение той же самой операции ему почему-то требуется гораздо больше времени.
При сборе данных в домашних условиях необходимо учесть, что и в этом случае вы делите канал с другими пользователями, например, членами семьи. В этом случае тесты лучше проводить тогда, когда в сети находится меньше пользователей — в рабочее время, поздно ночью или очень рано утром.
Совместное использование ресурсов приложениями на вашем компьютере также может повлиять на производительность браузера — по крайней мере так же сильно, как и совместное использование канала.
Это особенно заметно тогда, когда несколько программ зависят от внешних ресурсов или платформ. Например, некоторые антивирусы по-разному встраиваются в различные браузеры, разумеется, с не известной заранее степенью влияния на производительность.
Результаты тестирования двух браузеров одновременно, "бок о бок", могут оказаться совершенно некорректными. Например, платформа Windows имеет ограничение — возможны лишь 10 одновременных исходящих соединений; остальные запросы будут поставлены в очередь на выполнение по мере освобождения ресурсов и могут, в зависимости от необходимого временного интервала, завершиться успешно или с ошибкой. Такой способ тестирования означает, что вы, скорее всего, поставите один из браузеров в преимущественное положение тем, что запустите его на несколько микросекунд раньше соперника.
Можно рассмотреть всего два простых примера, хотя их существует множество. Не рекомендуется запускать другие приложения, когда вы тестируете производительность браузера. Вы должны предпринять следующие обязательные шаги для того, чтобы избежать влияния других программ:
%windir%\\system32\rundll32.exe advapi32.dll,ProcessIdleTasks
Кроме влияния совместного использования ресурсов и канала, на производительность может весьма значительно повлиять механизм работы серверов, к которым вы обращаетесь.
Одним из основополагающих принципов при проведении ваших тестов должно стать обеспечение равных условий на всех этапах и для всех аспектов тестирования. Для определения влияния процедур кэширования необходимо, чтобы серверы, к которым вы обращаетесь, накопили известное количество данных; для тестирования сети необходимо, чтобы среда выполнения тестов была изолирована от влияния внешних ресурсов.
Примером конструктивных особенностей приложения, способных оказать влияние на процесс тестирования, может служить программа управления банковским счетом. По соображениям безопасности такая программа получает доступ к данным только после того, как прошла авторизация пользователя. Цель тестирования состоит в сравнении поведения двух или более браузеров на веб-странице банка, содержащей такую программу. Для этого необходимо, чтобы программа находилась как бы в равных условиях по отношению к тестируемым браузерам.
Обычно такого рода программы не разрешают пользователям входить в систему одновременно из двух или более сессий: при повторной авторизации предыдущая сессия завершается (пользователь выходит из системы). Если предыдущее состояние веб-приложения не будет сброшено перед началом тестирования другого браузера, приложению может потребоваться дополнительное время для обработки повторного запроса, закрытия предыдущей сессии и запуска новой.
Все эти процедуры значительно влияют на процесс тестирования, и характерны не только для онлайновых банковских приложений, так что вам следует попытаться исключить их как фактор. В общем случае вы должны очень хорошо представлять себе особенности поведения тех веб-страниц, с помощью которых тестируете браузеры.
Во многих областях сам факт наблюдения изменяет характер поведения наблюдаемых объектов. Этот феномен получил наименование "эффекта наблюдателя".
Вы можете использовать любой набор библиотек для упрощения задачи тестирования некоторых сценариев использования браузера. Эти наборы обычно ориентированы на разработчиков и технически продвинутых пользователей. Как и в любом другом случае, когда результаты попадают в зависимость от способа измерения, необходимо тщательно оценить и по мере возможности ограничить колебания производительности, вызываемые типами библиотек, которые вы используете.
Время, необходимое для запуска браузера, может зависеть от многих факторов, и некоторые из них совершенно не относятся к качеству самой программы.
Как и в случае с кэшированием, время запуска браузера зависит от внешних условий, особенно если вы запускаете браузер первый раз. Прежде чем можно будет приступить к серфингу, необходимо, чтобы соответствующие модули браузера загрузились в оперативную память — процесс, требующий времени. Когда вы впервые загружаете браузер, трудно определить, какое количество необходимого кода уже находится в памяти. Особенно трудно это в случае с IE, поскольку многие его компоненты совместно используются другими приложениями.
Чтобы собрать наиболее непротиворечивые данные, откройте и закройте каждый браузер как минимум один раз перед тем, как начнете тестирование. Если все остальные приложения закрыты, это даст вашей операционной системе возможность загрузить нужные компоненты в память и обеспечит последовательность и точность результатов тестирования. Это также создаст равные условия конкуренции для разных браузеров, особенно в свете существования таких функций операционной системы, как Superfetch (http://www.microsoft.com/windows/windows-vista/features/superfetch.aspx), которая в ином случае обеспечит преимущества "любимому" браузеру.
Веб-сайты постоянно изменяются. К сожалению, это происходит и тогда, когда вам необходимо тестировать производительность браузера.
Можно на время тестирования закэшировать содержимое веб-страниц, чтобы гарантировать, что браузер всегда получает один и тот же контент для каждой итерации теста. В реальности такого, конечно же, не происходит.
Современные сайты обновляют содержимое очень часто. На
Вне лабораторного пространства контролировать такой характер изменений практически невозможно. Разумеется, существуют определенные подходы: например, вы можете использовать инструмент типа Fiddler для манипулирования контентом, который получает браузер. К сожалению, подобные методы с большой вероятностью могут привести к тому, что ожидаемого результата вы не достигнете. Решение состоит в том, чтобы следовать тем же рекомендациям, которые были даны выше относительно вопроса размера образцов для тестирования, и если вы обнаружите, что увесистый рекламный баннер появляется всякий раз после определенного количества загрузок страницы, то будет справедливо повторить измерения для обеспечения надежных результатов.
Изменение на веб-странице зависит не только от пользователя, но и от веб-мастера, который создал радикально отличающиеся версии своего ресурса для разных браузеров.
Один из подходов, используемых в том случае, когда необходимо обеспечить доставку одного и того же содержимого для ваших тестов — делать сайты, которые подстраиваются под тип браузера. В большинстве случаев следует игнорировать то, что для разных браузеров используется различный программный код. Это дает вам возможность реально оценить впечатления пользователей при посещении таких ресурсов.
Однако в некоторых случаях, в зависимости от того, каким браузером вы пользуетесь, изменяются и функциональные возможности страницы, причем настолько, что прямое сравнение браузеров становится невозможным. Существуют ресурсы, которые на самом деле предлагают различную функциональность в зависимости от типа браузера. Оценка производительности на базе таких сайтов — непростая задача, но стоит избегать прямого сравнения браузеров при их посещении, поскольку речь в этом случае идет более о взглядах и предпочтениях дизайнеров, нежели о производительности браузеров как таковых.
Определить, какие сайты ведут себя подобным образом, не всегда просто, и в этом случае веб-дизайнеры имеют преимущество перед "обычными" тестировщиками. Они должны использовать профайлеры, отладчики и другие имеющиеся в их распоряжении инструменты для того, чтобы определить участки контента, на которых разные браузеры ведут себя существенно отличающимся образом.
Рядовым тестировщикам и пользователям без глубоких технических познаний не следует оценивать производительность браузеров на примерах сайтов, слишком по-разному представляемых в разных браузерах, поскольку им сложно провести грань между производительностью сайта и особенностями его дизайна.
Можете ли вы точно определить, что означает "веб-страница загружена"? Как быть в случае, если она содержит сложные AJAX-сценарии?
Проблема при оценке производительности заключается в определении того, что, собственно, означает надпись "готово" в статусной строке браузера при загрузке страницы. А также в том, что некоторые страницы усложняются и разрастаются несогласованно друг с другом. Некоторые веб-программисты применяют маркер "загружено" (http://www.w3.org/ TR/htmL401/interact/scripts.htmL#h-18.2.3) как индикатор того, что браузер завершил разметку содержимого страницы для последующей загрузки. Этот маркер, к сожалению, интерпретируется разными браузерами по-разному.
Кроме индикаторов, работающих на уровне программного кода, некоторые используют, например, прогресс-бар браузера, текстовые поля и прочие общепринятые элементы графического интерфейса. Как правило, поведение этих элементов никак не регламентировано, и веб-программисты могут по своему усмотрению менять его, определяя, когда (если вообще!) отображать их на экране.
В таких ситуациях тестировщикам и пользователям
стоит применять те методы приближения, которые
основываются на индикаторе загрузки, с тем чтобы наблюдать поведение этого индикатора в процессе загрузки страниц.
Например, вы измеряете скорость первоначальной загрузки определенной страницы, и в то же время взаимодействуете с ее содержимым. Если страница
все еще как будто бы загружается, индикатор показывает "в прогрессе", но при этом вы можете взаимодействовать с контентом. Вы можете решить не обращать внимания на индикатор и
ориентироваться на визуальные параметры оценки, загрузилась страница до конца или нет. С другой стороны, индикатора загрузки может оказаться достаточно для предварительной оценки
скорости загрузки в различных браузерах. Однако если скорость, с какой страница загружается в действительности, не соответствует индикации в прогресс-баре, то будет довольно трудно понять,
до какой степени в этом случае можно доверять результатам
Использование надстроек означает, что с этого момента вы измеряете производительность не только самого браузера. Надстройки могут существенно влиять на производительность. Согласно данным, полученным из источников в Microsoft, IE используется в совокупности с дюжинами надстроек (относительно Mozilla ситуация абсолютно идентичная).
Любая из этих надстроек может проявлять произвольную активность внутри браузера. Иллюстрацией воздействия может служить следующая ситуация: пользователи, отстаивающие свои предпочтения в отношении определенного браузера, внезапно обнаруживают, что любая альтернативная программа работает быстрее лишь потому, что их любимый браузер перегружен надстройками и дополнениями, а альтернативный представляет собой чистую, без всякого "мусора" программу. Например, пользователь обремененного несколькими дополнениями Firefox может сменить его на IE, увидев, что тот работает быстрее, а в это время пользователь IE переходит на Firefox по той же схеме исходя из тех же причин. Здесь нет никакого противоречия — такие примеры лишь демонстрируют решающее влияние надстроек.
Для блокировки надстроек в IE 8 необходимо вызвать пункт Manage add-ons из меню Tools. В появившемся диалоговом окне выберите All Addons и последовательно заблокируйте все надстройки из списка. Если вы дружите с командной строкой, можете выполнить команду iexplore.exe -extoff для запуска IE без дополнений.
Поскольку большинство программистов предпочитает не сокращать объем кода, чтобы тем самым гарантировать ожидаемое поведение надстроек после обновлений, важно, особенно во время использования пробных версий, иметь возможность отключить любое из некорректно ведущих себя дополнений.
В этом разделе рассматривается часть прикладных методов, положенных в основу разработки YASS (http://yass.webo.in/) — самой быстрой библиотеки для выбора элементов по CSS-селекторам.
Начнем с наиболее очевидной составляющей любой логики — ветвления. В любом алгоритме встречается место, в котором нужно выбрать то или иное продолжение в зависимости от проверяемого условия. Давайте рассмотрим следующие простые примеры проверки. В первом случае у нас три простых вложенных проверки:
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, не
if (a b c) {
...
}
Очень часто нам нужно проверить что-то более сложное, чем просто число. Например, совпадение строки с заданной или равенство объектов. В этом случае нам просто необходимо следующее сравнение:
var a = 1,
b = 2,
c = '3';
if (a == 1 b == 2 c === '3') {
...
}
Здесь мы используем сравнение без приведения типов ===, которое в случае нечисловых переменных работает быстрее обычного сравнения на 10—20%.
Достаточно часто нам нужно выбрать одну из условных ветвей, основываясь на заданной строке. Обычно для этого используются либо методы объекта , либо
строковые методы ( match, search, indexOf ). Если нам нужно просто проверить соответствие строки какому-то регулярному выражению, то лучше всего для этого подойдет именно test:
var str = 'abc',
regexp = new RegExp('abc');
if (regexp.test(str)) {
...
}
Такая конструкция отработает на 40% быстрее, чем аналогичный exec:
if (regexp.exec(str)[1]) {
...
}
Строковый метод match аналогичен методу exec у создаваемого объекта
В том случае, если регулярное выражение требуется "на один раз", подойдет более быстрая (примерно на 10% относительно варианта с инициализацией нового объекта) запись:
if (/abc/.test(str)) {
...
}
Если же, наконец, нам нужно проверить просто нахождение подстроки в заданной строке, то тут бесспорным лидером будет именно indexOf, который работает в два раза быстрее разбора регулярных выражений:
if (str.indexOf('abc') != 1) {
...
}
Давайте теперь рассмотрим следующий вариант регулярного выражения: /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 {
...
}
switch (firstLetter) {
case '#':
...
break;
case '.':
...
break;
case ':'
...
break;
case '[':
...
break;
default:
...
break;
}
switch (ancestor) {
case ' ':
...
break;
case '∼':
...
break;
case '+':
...
break;
case '>':
...
break;
}
first-child, last-child, nth-child,
и т. д.) и
выбор проверочной функции для атрибутов ( $$\sim$$ =, *=, = и т. д.) осуществляется уже через специальные хэши:_.attr = {'': ... , '=': ... , '=': ... , '^=': ... ...}
Подводя небольшой итог для различных способов проверки строки, можно составить такую таблицу:
| Задача | Средство решения |
|---|---|
| Проверка числового значения | Обычное сравнение (==) |
| Проверка нескольких числовых значений | Сравнение их суммы |
| Проверка, что число не нуль, или проверка на существование | Проверка отрицания к заданной переменной (!) |
| Разбор строки и выделение частей в массив | String.match( или |
| Проверка строки на соответствие регулярному выражению | |
| Проверка строки на точное соответствие (либо соответствие одному из набора значений) | if без приведения типов (===) |
| Выбор в зависимости от точного значения (значений 1—2) | Условная конструкция if |
| Выбор в зависимости от точного значения (значений 3—8) | switch |
| Выбор в зависимости от точного значения (значений больше 8) | Хэш с ключами, соответствующими значениям |
Наверное, данную таблицу можно дополнить еще некоторыми случаями или же обратиться к статье, посвященной производительности простых конструкций в JavaScript
На данный момент насчитывается несколько десятков различных
Но в последнее время появилось несколько явных фаворитов на
этом поприще. Речь идет про Sizzle (движок выборки элементов, автором
которого является Джон Ресиг и который включен в
Поэтому возникает резонный вопрос, почему нельзя сделать быстрое мини-ядро для CSS-селекторов, которое обеспечит базовую функциональность для работы с DOM (например, совсем базовую — просто выборку элементов)? И, самое главное, чтобы это работало не медленнее (в идеале даже быстрее), чем вызовы в самом браузере.
Но описанная задача имеет решение. Можно создать библиотеку, которая будет осуществлять базовые операции практически так же быстро, как и сам браузер (а в некоторых случаях даже быстрее — за счет кэширования). И это удалось сделать в достаточно сжатые сроки. Далее речь пойдет о самой быстрой (на момент написания книги) библиотеки для выбора элементов по CSS-селекторам. Каковы же были причины для написания такой библиотеки?
Во-первых, такой код должен и кэшировать выборки (DOM-вызовы дорого обходятся, все нормальные JavaScript-программисты уже их кэши-руют — так упростим им задачу и повысим быстродействие).
Во-вторых, есть статистика использования CSS-селекторов в проектах — значит, у нас есть набор элементарных операций, которые должны выполняться быстрее всего (может быть, даже в ущерб более общей производительности).
В-третьих, библиотека должна возвращать сами элементы, чтобы к ним можно было "прикрутить" любые обертки и нарастить методы. Все обертки ресурсоемки. Если нужно просто 200 раз поменять HTML на странице у заданных элементов, то они не нужны. Достаточно и проверок со стороны браузера на допустимость выполняемых операций.
В-четвертых, естественно, библиотека должна опираться на все самое быстрое: быстрые итераторы, быстрые подстановки, анонимные функции, минимум вложенных вызовов и т. д.
Синтаксис такой библиотеки до безобразия прост:
_(‘p’) — вернет все параграфы на странице;_(‘p a’) — или все ссылки в них;_(‘p a.blog ’) — или все ссылки с классом blog .Может быть, кому-то данная разработка покажется тривиальной, но дальнейшее развитие ситуации видится следующим образом. Код YASS можно использовать в образовательных целях, чтобы понять логику конечных автоматов, используемых браузером для распознавания CSS-се-лекторов (во встроенном движке), и промоделировать некоторые тривиальные или не очень ситуации.
Или же данный код можно будет доработать и включать в основу высокопроизводительных библиотек, которые уже будут при его помощи ре-ализовывать свои методы (одной из таких библиотек,
с которыми YASS уже интегрирован, является js-core, http://code.googLe.eom/p/js-core/). Также возможна замена кодом YASS
встроенного механизма выборки элементов по CSS-селектору в таких распространенных библиотеках, как MooTooLs,
Но давайте рассмотрим процесс выборки CSS-селекторов более детально.
Начнем с самого простого: чего мы хотим добиться? Мы хотим, задав произвольную строку CSS-селектора, соответствующую спецификации (http://www.w3.org/TR/2005/WD-css3-seLectors-20051215/), получить на выходе массив из всех элементов, соответствующих этой самой строке. Вроде пока все просто.
В качестве иллюстрации спецификации можно привести следующие примеры (работает во всех современных браузерах и IE8+):
// вернет элемент с идентификатором my_id
querySelectorAll('#my_id')
// вернет все элементы с классом external
querySelectorAll('.external')
// вернет все абзацы на странице
querySelectorAll('p')
Однако уже тут можно отметить один момент: очень часто нам нужно
выбрать просто элемент по его идентификатору или найти все элементы с
определенным классом. Эти операции встречаются достаточно часто во
всех
Если посмотреть на современные JavaScript-библиотеки, то везде такая проверка уже осуществляется с помощью регулярного выражения. И тут сразу же небольшая хитрость: инициализация любого регулярного выражения обходится достаточно дорого в браузерах (хотя сложность выражения начинает влиять на затраченное время только при большой его длине), и разумно было бы вообще без него обойтись. И когда можно обойтись indexOf для проверки подстроки — этим (в элементарных случаях) всегда нужно пользоваться.
Может так случиться, что регулярное выражение обеспечивает корректность, и замена его никак не приведет к потере читаемости кода и непонятному выигрышу (или даже проигрышу) в скорости выполнения. Тогда можно заменить, например, exec на связку test и charAt / substr: это позволит увеличить производительность примерно на 20%. Если данный участок кода выполняется в цикле многократно, ускорение может оказаться достаточно существенным.
В YASS данная задача решена следующим образом:
// проверяем, удовлетворяет ли селектор простому случаю
if (/^[\w[:#.][\w\]*^|=!]*$/.test(selector)) {
// в случае положительного результата инициализируем переменную,
// которая отвечает за '#', '.' , ':' или '[' в начале селектора
var firstLetter = selector.charAt(0);
...
}
Но давайте рассмотрим, как решена общая задача по разбору CSS-селекторов. Если принять во внимание, что селектор может быть задан в
виде p a.link, form input[type=radio], то логику его разбора можно
схематично записать в следующем виде:?
sets ).p a.link ). Нам нужно разбить последовательность на
части и разобрать каждую такую часть, учитывая, что родительскими
элементами для следующей части будут выбранные элементы из
предыдущей. За "превращение" дочерних узлов в родительские
(прямо процесс взросления получается) отвечает массив nodes.single = regexp.exec(single); tag = single[1]; id = single[2]; ...
Как мы видим, основная логика данной задачи включает как мини-
мум одно регулярное выражение (использование indexOf и substring
будет при такой сложности намного более ресурсоемко) и 3 цикла (которые
нужно сделать максимально быстрыми). Не стоит перечислять все возможности быстрого выбора элементов, просто сделаем акцент на
некотоых аспектах.
Пусть у нас объявлен некоторый массив 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;
}
Именно такой код используется для сброса у элемента флага, отмечающего состояние "выбран". Давайте рассмотрим, зачем этот флаг нужен и зачем его сбрасывать.
Одной из основных проблем в ходе выбора элементов по 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).
YASS (http://yass.webo.in/) создавалась и продолжает разрабатываться для поиска и реализации наиболее производительных методов для решения определенного круга задач. Ее можно применять как в учебных целях, так и в чисто практических (например, для составления набора неиспользуемых на сайте CSS-селекторов — с помощью YASS это реализуется быстрее всего).
Данный раздел написан под впечатлением от статьи DOM
Предварительная версия документа API для селекторов (http://dev.w3.org/2006/webapi/selectors-api/), опубликованная консорциумом W3C, представляет собой относительно новый взгляд для JavaScript-разработчиков на то, как можно выбирать DOM-элементы на страницы при помощи CSS-селекторов. В одном этом документе собраны все тонкости такого сложного процесса, как поиск, выборка элементов из DOM-дерева и представление результата, доступного по упорядоченному интерфейсу.
Несмотря на все недавние войны по поводу интеграции стандартов в браузеры, этот является одним из наиболее поддерживаемых: его можно использовать прямо сегодня в браузерах Internet ExpLorer 8, Chrome и Safari, а также в Firefox 3.5 и Opera 10.
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 для селекторов весьма просто
(каждый принимает только один аргумент на вход), проблемы наступают
при выборе подходящей
Итак, самой большой проблемой для новых пользователей API для
селекторов является выбор корректной CSS-спецификации для использования. Особенно это актуально для большинства разработчиков, который
пишут кроссбраузерный код и поэтому ориентируются на
Изучение спецификаций
Наиболее часто встречающийся случай применения API для CSS-се-лекторов — это использование его не напрямую, а при помощи разнообразных сторонних библиотек, которые также обеспечивают функциональность CSS-селекторов для DOM. Сегодня основная проблема внедрения применения API для селекторов заключается в том, что они не поддерживаются во всех браузерах, для которых ведется разработка (в частности, это IE 6, IE 7 и Firefox 3). Поэтому пока эти браузеры еще не вышли из обращения, нам будут требоваться некоторые промежуточные утилиты для восстановления недостающей функциональности CSS-селекторов для DOM.
Однако, к счастью, на данный момент таких библиотек — огромное число, и все они поддерживают интерфейс выбора элементов, совместимый с API для селекторов API (на самом деле последнее возникло как раз из рассмотрения текущей ситуации с выбором элементов и предложением интеграции в браузеры некоторой часто требуемой функциональности). В дополнение к этому существует некоторое количество фреймворков, которые уже переключаются на API для селекторов при наличии его в браузере (поэтому вы можете совершенно спокойно использовать их и не думать о применении каких-либо более эффективных инструментов для ускорения клиентской части вашего сайта,). Это означает, что вы можете работать с CSS-селекторами прямо сегодня и получить все возможные преимущества от их повышенного быстродействия в некоторых браузерах за счет API для селекторов, и это обойдется вам совершенно бесплатно.
Некоторые из существующих фреймворков, использующих по возможности API для селекторов:
Стоит также подчеркнуть, что применение нового API влечет значительный выигрыш в производительности (по сравнению с обычными методами выбора элементов из DOM при помощи JavaScript).
Вы сможете самостоятельно убедиться в этом, просто судя по улучшению ситуации в
Согласно уже проведенным тестам результаты получаются примерно следующими:
(рис 6.3) Прирост в производительности после внедрения API для селекторов, источник: hacks.mozilla.orgНевооруженным взглядом виден прирост в производительности после внедрения использования нового API для селекторов — то же самое произойдет с вашими веб-приложениями, применяющими указанные фреймворки, в современных браузерах.
Чтобы сравнить определение спецификации API для селекторов (http://dev.w3.org/2006/webapi/seLectors-api/) с фактической реализацией, было создано специальное тестовое окружение (автор — Джон Ресиг из MoziLLa). Это тестовое окружение может быть в том числе использовано для проверки основных браузеров на уровень соответствия стандартам.
Текущие результаты для браузеров, которые поддерживают это API, следующие:
Internet Explorer 8, как уже было упомянуто ранее, не реализует логику CSS3-селекторов (наверное, в силу того, что спецификация еще не утверждена w3.org), поэтому проваливает большую часть тестов.
По всей видимости, API для селекторов должно обеспечить простой и быстрый путь для выборки DOM-элементов на странице. Это действительно здорово, что все JavaScript-библиотеки используют тот же самый синтаксис и обеспечивают ту же функциональность. Стоит постараться разобраться в этом сейчас и начать применять этот API.
Прежде чем начать разговор о текущем положении вещей с поддержкой векторной и скалярной графики в браузерах, стоит немного углубиться в историю.
В 1998 году компания Microsoft при поддержке других крупных производителей предложила W3C свой стандарт отображения векторной информации —
В этом же 1998 году Adobe, IBM, Netscape и Sun вносят в W3C предложение о рассмотрении своего стандарта в противовес Microsoft —
Сейчас большинство
<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>
Спецификация Canvas (как отдельной области на странице, внутри которой можно отображать графические объекты) в 2005 году изначально была предложена со стороны Apple для поддержки некоторых приложений внутри движка WebKit (на данный момент его используют браузеры Safari и Chrome). Рабочая группа W3C включила Canvas в Web Applications 1.0, который вошел в готовящийся стандарт HTML 5.0.
Сейчас встроенная поддержка Canvas реализована в том или ином виде во всех современных браузерах. В IE версии 8 и ниже она эмулируется при помощи отдельного
В качестве основного отличия от
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 (и, скажем,
Подробнее со спецификацией Canvas можно ознакомиться, например, на странице WHATWG (http://www.whatwg.org/specs/web-apps/ current-work/muLtipage/the-canvas-eLement.htmL).
Примеров использования Canvas довольно много, и они частично охватывают уже известные области 2D-графики.
2D-проекции 3D-объектов
С помощью Canvas можно изображать 2D-проекции трехмерных объектов. В силу того, что все преобразования выполняются на пиксельном уровне, достаточно просто создать необходимые интерфейсы для изометрических проекций трехмерных объектов. Видимо, уже скоро на основе Canvas появятся полноценные 3D-экскурсии в браузерах или онлайн-игры "от первого лица".
(рис 6.5) Изображение чайника при помощи Canvas, источник: www.nihilogic.dkTypeface.js
В качестве следующего применения стоит упомянуть библиотеку
Для применения библиотеки
Для больших порталов (где размер страницы составляет 500-1000 Кб) данный подход вполне приемлем (если "стилизованных" заголовков будет не так много, чтобы лишний раз не нагружать браузер преобразованиями страницы). Для небольших сайтов загрузка нескольких десятков Кб JavaScript-кода для стилизации 10 Кб HTML- и CSS-кода выглядит не очень уместной.
(рис 6.6) Применение Canvas для стилизации шрифтов, источник typeface.neocracy.orgCufo’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.comRaphaеl
Если предыдущий пример был посвящен использованию Canvas больше
в развлекательных целях, то библиотека Raphael.js (http://raphaeljs.com/)
преследует сугубо практические цели (хотя и делает это с помощью SVG +
Применение этой библиотеки предельно просто: обычно нужно объявить необходимые данные и задать один из множества доступных представлений (или создать свое собственное). Более подробно с данной библиотекой можно ознакомиться на ее официальном сайте —http://raphaeljs.com/.
На данный момент 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. Также будет необходимо дублировать
всю функциональность через
Данный раздел написан под впечатлением статьи Джона Ресига "Computing with JavaScript Web Workers" (http://ejohn.org/blog/ web-workers/), в которой он раскрыл особенности встроенного в браузеры механизма "отложенных" вычислений при помощи JavaScript и его будущие перспективы.
Последняя спецификация Web Workers, несомненно, является наиболее перспективной из грядущих нововведений в браузерах, которая позволяет исполнять JavaScript-задачи параллельно, не блокируя интерфейс браузера.
Обычно для того, чтобы добиться более-менее приемлемых вычислений при помощи JavaScript-движка, необходимо было разбивать всю работу на небольшие части и запускать их друг за другом при помощи таймера. Это как не самый быстрый, так и совершенно не эффективный путь достижения поставленной задачи.
Имея в виду текущее положение вещей, давайте углубимся в спецификацию Web Workers.
Рекомендация Web Worker (http://www.whatwg.org/specs/web-workers/ current-work/) частично основывается на
уже проделанной работе со стороны команды
Worker — это скрипт, который может быть загружен и исполнен в фоновом режиме. Web Workers позволяют легко это сделать, например:
В этом примере будет загружен скрипт, расположенный по адресу worker.js, а его выполнение будет произведено в фоновом режиме (скорее всего, браузеры будут использовать встроенные средства операционной системы — нити — для реализации такого поведения).
Однако есть несколько серьезных "подводных камней":
document,
getElementById и т. д. (Наиболее важными исключениями из
этого правила будут методы Имея в виду эти ограничения, стоит сразу задаться вопросом: а для чего, собственно, может пригодиться Worker и какие задачи он способен решать?
Вы можете использовать Worker, обмениваясь с ним сообщениями.
Все браузеры (которые поддерживают данную спецификацию)
позволяют обмениваться строковыми сообщениями (Firefox 3.5 также
поддерживает обмен
Обмен сообщениями производится при помощи 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.14) Расчет освещения при помощи Web Workers, источник ejohn.orgЗдесь Canvas применен для отрисовки рассчитанной сцены. Если включить Web Workers, то заметно, как картинка отрисовывается по частям. Это происходит благодаря разбиению всей работы на части и поручению каждого набора пикселей отдельному Worker. Этот Worker затем подает массив цветов для отрисовки на Canvas, а родительская страница их применяет. (Заметьте, сам по себе Worker ничего не изменяет.)
(рис 6.15) Отслеживание движения при помощи Web Workers, источник: ejonh.orgВ этом случае применяется несколько технологий: элемент video,
элемент canvas и отрисовка кадров видео на сам холст. Все отслеживание
движения производится в фоновом режиме при помощи Web Workers (по-
этому передача видео не останавливается и не блокируется).
(рис 6.16) Эмуляция огня при помощи Web Workers, источник: ejohn.orgЭтот пример пытается ограничить несколько случайных точек, используя алгоритм эмуляции огня (simulated annealing). Также здесь приведено анимированное PNG-изображение (работает в Firefox 3.5), которое вращается, пока идет вычисление в фоновом режиме.
Недавно закончилось интересное соревнование от 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).
По этим ссылкам можно посмотреть на демонстрацию (автором которой является Джон Ресиг) и скачать исходные коды:
Чем больше сайтов становятся веб-приложениями, тем более возрастает потребность хранить какие-то данные на компьютере пользователя. Чаще всего речь идет о кэшировании, особенно если приложение предоставляет возможность работать без доступа к Интернету.
Хранение на стороне пользователя данных — настроек приложения, его состояния, частей кода и прочего, — способно сильно разгрузить канал, увеличить скорость загрузки и улучшить время реакции приложения на действия пользователя.
Первопроходцем на ниве сохранения данных на клиенте можно назвать компанию 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 получила дальнейшее развитие за прошедшие годы, стандарт расширился, но поскольку управление этими расширениями из клиентских скриптовых языков недоступно, рассматривать их мы не будем.
Компания 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();
В марте 2002 года появилась шестая версия Flash, популярнейшего
плагина для браузеров, установленного, по статистике, на 95% компьютеров.
В этой версии появилось собственное хранилище — Local Shared
Object, позволяющее хранить до 100 Кб данных без ведома пользователя
и любой объем сверх этого с его разрешения. У роликов с одного домена
единый Local
Способ использования этого типа хранилища — установка на странице Flash-ролика, который будет обмениваться данными со скриптовым языком. Вплоть до восьмой версии Flash не существовало хорошего способа обмена данными со скриптовыми языками браузера, пока в Flash8 не появился ExternalInterface, реализованный, впрочем, с существенными ошибками.
Хотя и прежние способы, более похожие на хаки, позволяют производить обмен (по крайней мере теоретически), универсальные библиотеки для доступа к клиентским хранилищам, которые будут рассмотрены далее, требуют для своей работы восьмую версию Flash.
Рабочая группа WHATWG была
организована производителями
браузеров Apple, Mozilla Foundation
и Opera Software
В частности, этот документ решал и проблему хранения данных веб-приложения на стороне пользователя. Текущая спецификация 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 (обратите внимание на регистр букв!) также есть
опциональные аргументы: после текста запроса и его подстановок можно указать
Спецификация на globalStorage присутствует в ранних версиях черновика HTML5, откуда ее удалили из соображений безопасности. Основная идея globalStorage — дать разработчикам возможность
обращаться к данным
Этот вид хранилища был реализован в Firefox 2 (и в Internet Explorer 8.0 beta 1), но уже в следующей версии браузера возможность обращаться к данным
В настоящий момент 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)
}
}
Последнее из рассматриваемых нами хранилищ —
Основная идея хранилища
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();
}
Как видно, существенную часть занимает инициализация
Авторы не обладают сведениями об ограничениях на размер этого хранилища, вполне может быть, что никаких ограничений, помимо тех, что есть в самом SQLite, не существует.
Итак, мы рассмотрели основные из имеющихся на сегодняшний момент в распоряжении программиста клиентских хранилищ. Такое обилие решений, большинство из которых поддерживаются ограниченным набором браузеров, не могло не привести к появлению специализированных библиотек для работы с клиентскими хранилищами. Не умаляя полезности минимальных знаний о работе каждого хранилища, мы все же рекомендуем для доступа к ним использовать одну из готовых библиотек, которые будут рассмотрены ниже.
Первый недостаток — нет возможности изменить последовательность, в которой PersistJS перебирает методики хранения данных. К примеру, если вы решили поставить localStorage выше
Другая проблема — если библиотека обнаружила, например, хранилище
Еще недоработка — PersistJS не проверяет готовность Flash-ролика, и в том случае, если вы используете это хранилище, есть небольшая вероятность того, что ваш код попытается обратиться к данным еще до того, как ролик будет загружен. Другая связанная с Flash-роликом проблема — библиотека не умеет запрашивать дополнительное место для хранения данных, если 100 Кб, которые можно использовать без запроса разрешения, исчерпаны.
Неприятно также, что библиотека позволяет сохранять только текстовые строки, не предоставляя возможности сериализации, впрочем, версия из репозитория умеет представлять сложные объекты в виде
Впрочем, у библиотеки хороший плюс — небольшой (около 9 Кб) размер.
Этот фреймворк лишен недостатков PersistJS, но есть изъян — гигантский размер: версия 1.3.2 занимает 45 Мб. Конечно, для работы с хранилищем весь фреймворк не нужен, но даже минимально необходимый набор занимает более 100 Кб.
Существует адаптированная версия Dojo Storage, которая называется SRAX Storage (http://fullajax.ru/#:download/), но она поддерживает только Flash Local
К сожалению, даже создатели 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 |
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 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 - создатели
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.