Опубликован: 16.06.2010 | Уровень: специалист | Доступ: платный
Лекция 9:

Практическое приложение

< Лекция 8 || Лекция 9: 12345678

8.6. Пара советов для Ruby on Rails

Уже много людей писали руководства, помогающие вашему веб-приложению работать быстрее. В этом разделе будут освещены самые простые, но наиболее эффективные методы, которые дадут вам возможность существенно ускорить ваше приложение без потери какого-либо функционала из Ruby on Rails.

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

Решение заключается в том, чтобы уменьшить количество внешних ресурсов на вашей странице, объединив их все в один файл. Поможет нам в этом плагин AssetPackager (http://synthesis.sbecker.net/pages/asset packager). Ставим

script/plugin install git://github.com/sbecker/asset_packager.git

Пример config/asset_packages.yml:

javascripts:
- base:
- prototype
- effects
- controls
- dragdrop
- application
- secondary:
- foo
- bar
stylesheets:
- base:
- screen
- header
- secondary:
- foo
- bar

И запускаем rake-задачу:

rake asset:packager:build_all

Дальше для JavaScript пишем

<%= javascript_include_merged :base %>

или

<%= javascript_include_merged 'prototype', 'effects', 'controls',
'dragdrop', 'application' % >

Для стилей пишем:

<%= stylesheet_link_merged :base %>

или

<%= stylesheet_link_merged 'screen', 'header' %>

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

<script type="text/javascript" src="/javascripts/prototype.js"></script>
<script type="text/javascript" src="/javascripts/effects.js"></script>
<script type="text/javascript" src="/javascripts/controls.js"></script>
<script type="text/javascript" src="/javascripts/dragdrop.js"></script>
<script type="text/javascript" src="/javascripts/application.js"></script>
<link href="/stylesheets/screen.css" type="text/css" />
<link href="/stylesheets/header.css" type="text/css" />

А в режиме рабочего сайта будет:

<script type="text/javascript" src="/javascripts/base_packaged.js?123456789"></script>
<link href="/stylesheets/base_packaged.css?123456789" type="text/css" />

Теперь, чтобы сделать нагрузку еще меньше, переносим все свои статические файлы на другой хост. В Ruby-on-Rails это очень просто сделать, достаточно добавить в config/environments/production.rb такую строку:

config.action_controller.asset_host = "http://assets.example.ru"

Теперь все image_tag, javascript_include_tag и т. д. будут указывать на этот хост.

8.7. Разгоняем jQuery

Материал для данного раздела получен при общении с Олегом Смирновым (aka CTAPbIu_MABP) — разработчиком пользовательских интерфейсов на Java и JavaScript. На данный момент он трудится над системой самообслуживания Украинского мобильного оператора "Киев-стар" (http://my.kyivstar.ua/). Олег занимается исследованиями в области производительности JavaScript-библиотек, в частности, jQuery, чему посветил много статей на своем сайте (http://mabp.kiev.ua/).

jQuery, пожалуй, самая известная JavaScript-библиотека. Она позволяет быстро и просто производить манипуляции с DOM-деревом, навешивать события и делать AJAX-запросы на сервер. Ею пользуются очень много компаний, в том числе GoogLe и Microsoft. Под нее написано огромное количество плагинов, позволяющих расширить стандартный функционал и добавить на страницу виджет любой красоты.

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

8.7.1. Selectors

Стоит начать с функции $, она принимает два параметра: первый — селектор, второй — контекст. Хотя контекст обычно опускают, впоследствии будет показано, как им грамотно пользоваться.

Простой селект

Самый простой вариант — это выбор по id, имени тега и имени класса.

$("#id")
$("tag")
$(".class")

Не случайно они расположены именно в такой последовательности: они идут по сложности алгоритма выборки. В первом случае вызов функции эквивалентен вызову

document.getElementById("id");

Поскольку предполагается, что id уникальный, поиск проходит очень быстро, и если на странице есть два элемента с таким id, то найден будет только первый. Хотя в IE и тут сделали ошибку и до 7-й версии включительно в случае отсутствия элемента с таким id он вернет элемент, у которого совпадает атрибут name.

Во втором случае тоже все относительно просто:

document.getElementsByTagName("tag");

Получили все ноды с таким именем из документа, и все готово. И на удивление никаких ошибок, если не учитывать, что при запросе getElementsByTagName("*") IE вернет и комментарии тоже.

В третьем случае, если есть возможность, работу перехватывает

document.getElementsByClassName("class");

(Таблицу поддержки этой функции в браузерах можно посмотреть на quirksmode.org)

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

var nodes = document.getElementsByTagName("*"), result = [];
for (var i=0; i<nodes.length ; i=""
  if="" ( " " + (nodes=""[i=""].className=""
nodes=""[i=""].getAttribute=""
 .indexOf=""("class") >-1)
  result.push(nodes[i]);
        }

Какой метод применять, определяется в самом начале при подключении библиотеки.

Селект через querySelectorAll

приходится обычно использовать намного более сложные конструкции. И для них в современных браузерах FireFox 3.0, Safari 3.2, Opera 9.5, а также в IE8, появились функции querySelector и querySelectorAll. Они, соответственно, предназначены для поиска одной или нескольких нод по CSS3-селекторам. Если браузер клиента поддерживает эту функцию, то все, о чем написано в прошлом пункте, — отпадает, и поиск происходит через querySelectorAll.

$("#id .class tag")

В лучшем случае селектор будет обработан именно querySelectorAll, потому что он написан по правилам CSS3. Но такое возможно не со всеми селекторами: jQuery поддерживает ряд селекторов, которые не входят в CSS3, такие, например, как : visible.

$("#id .class tag:visible")

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

$(document).find("#id").find(".class").find("tag").filter(":visible")

Скорость этого метода поиска напрямую зависит от величины DOM дерева: чем оно больше, тем медленнее, — но ее можно значительно увеличить, написав селектор раздельно.

$("#id .class tag").filter(":visible")

При этом querySelectorAll выберет все ноды, а Sizzle разберется с ":visible".

По поводу псевдо-селекторов возникает также очень интересный вопрос: CSS3 поддерживает несколько видов псевдо-классов, такие как :nth-of-type/:nth-child/:parent/:not/:checked, jQuery имеет свою реализацию этих селекторов для браузеров, не поддерживающих querySelectorAll, или для браузеров, в которых querySelectorAll не поддерживает данный селектор, но эта реализация иногда отличается.

Для примера возьмем псевдо-класс : nth-of-type и выберем все четные дивы, а из них все нечетные.

document.querySelectorAll("div:nth-of-type(even):
nth-of-type(odd)")
// Safari/FireFox:0 IE/Opera:N/A
$("div:nth-of-type(even):nth-of-type(odd)");
// Safari/FireFox:0 IE/Opera:All
$("div:even:odd"); // All: вернут 1,5,9 дивы

Первых два примера работают одинаково и вернут либо 0, если отра- ботала функция querySelectorAll (это касается первого примера), либо все элементы, потому что их обработал Sizzle (это особенность реализа- ции выражения ":"). Третий же вернет 1, 5, 9 и т. д. элементы, а значит, селекторы отрабатывали в три прохода: сначала из всего DOM-дерева бы- ли выбраны все дивы, потом из них были выбраны все нечетные, а потом из оставшихся были выбраны все четные.

jQuery также имеет набор псевдо-селекторов, которые не входят в CSS3 и обслуживаются только Sizzle’ом : visible/:animated/:input/:header. Их лучше выделять отдельно, поскольку они могут сильно замедлить выборку. Так, например, было с селекторами :visible/:hidden в версии 1.2.6: для то- го чтобы узнать, видимый это элемент или нет, надо было подняться до са- мого верха по DOM-дереву, проверяя атрибуты display и visible каждого ро- дителя (http://mabp.kiev.ua/2009/02/07/accelerates-selectors-in-jquery/).

$("div").filter(":visible")

Псевдо-классы, используемые для поиска элементов формы, такие, как :radio, тоже имеют некоторое преимущество, если не применяется querySelectorAll; в противном случае CSS3-селектор input[type=radio] работает быстрее.

Сложенный селект

Сложенный селект — это когда нам надо выбрать группу из двух или более разных селекторов, например, все дивы, у которых класс равен A, B и C.

Это можно сделать двумя способами:

$(".a,.b,.c")

выбрать все сразу

$(".a").add(".b").add(".c")

или по одному.

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

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

$("[class^=my]")

а не городить логику с использованием add, тем более что такой способ поддерживается querySelectorAll (http://mabp.kiev.ua/2009/02/21/testingproductivity-jquery-selectors/).

Неправильный селект в контексте

На сайте tvidesign.co.uk в одной очень популярной статье "Improve your jQuery — 25 excellent tips" написано, что селект лучше делать в контексте, и приведен вот такой пример:

$(‘#listItem’ + i, $(‘.myList’))

Рассмотрим подробнее: контекст — это то, где ищут селектор, значит, пример можно переписать в более наглядную, но менее читаемую форму:

$($(".myList")).find("#listItem")

При этом контекст от первого поиска будет являться document.

$($(".myList",document)).find("#listItem")

Еще раз перепишем согласно формуле

$($(document).find(".myList")).find("#listItem")

И наконец, раскроем скобки

$(document).find(".myList").find("#listItem")

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

Правильный селект в контексте

Правильно делать "с точностью до наоборот". В контексте надо ука- зывать id элемента.

$(".class",$("#id"))

Однако можно не передавать в контекст jQuery объект, вполне достаточно

$(".class","#id")

Это можно переписать как

$("#id").find(".class")

Можно еще больше ускорить работу, если искать вот таким способом:

$(document.getElementById("id")).find(".class")

Но это, скорее всего, будет уже дурным тоном. Хотя поэкспериментировать интересно: что, если вместо getElementById взять querySelectorAll?

$("div",document.querySelectorAll("#id"))

Это примерно то же самое, что и

$("div",[document.getElementById("id")])

Ни прироста производительности, ни красоты кода из этого не получить, поэтому советую в контекст передавать что-то простое вроде id или при использовании псевдо-селекторов, обрабатываемых Sizzle’ом, пере давать их в селектор а все остальное в контекст:

$(":visible","input[type=checkbox]")

Раз уже пошла речь о псевдо-селекторах, то

$(":checkbox")

быстрее чем

$("input[type=checkbox]")

без использования querySelectorAll и наоборот.

Cложный селект

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

$("#id > div")

Но если выборка уже есть, то будем использовать ее как контекст. Как мы уже выяснили, поиск в контексте происходит при помощи функции find:

$("#id").find("> div")

Но find — очень дорогая функция, она просматривает абсолютно всех потомков контекста, поэтому лучше применить функцию children, она просматривает только непосредственных потомков.

$("#id").children("div")

Есть еще ряд функций поиска и манипуляций, которых стоит избегать без крайней необходимости, — это find, closest, wrap, wrapInner, replaceWith, clone. Стоит заметить, что wrapAll сюда не входит (http://mabp.kiev.ua/2009/03/29/jquery-profiling/).

< Лекция 8 || Лекция 9: 12345678
Ольга Артёмова
Ольга Артёмова

Доброго времени суток!

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

Сертификация: оптимизация и продвижение web-сайтов.

Ярославй Грива
Ярославй Грива
Россия, г. Санкт-Петербург
Ёдгор Латипов
Ёдгор Латипов
Таджикистан, Кургантепа