Селекторы и Sizzle
В качестве «поисковика» по элементам DOM jQuery использует библиотеку Sizzle. Данная библиотека одно время была неотъемлемой частью jQuery, затем «отпочковалась» в отдельный проект, который с радостью используется как самим jQuery, так и Dojo Toolkit. Но хватит лирики, давайте перейдем непосредственно к поиску. Для оного в JavaScript'е предусмотрены следующие методы (не в jQuery, не в Sizzle, а в JavaScript):
getElementById(id)
поиск по id="…"
getElementsByName(name)
поиск по name="name"
и id="name"
getElementsByClassName(class)
поиск по class="class"
getElementsByTagName(tag)
поиск по имени тега
querySelectorAll(selector)
поиск по произвольному CSS-селектору
Пробежавшись беглым взглядом по списку, можно заметить метод querySelectorAll()
– он универсален и действительно удобен. И именно этот метод библиотека пытается вызвать, когда вы скармливаете что-то в качестве селектора в jQuery, но данный метод иногда нас подводит, и тогда на сцену выходит Sizzle во всей красе, вооруженный всеми упомянутыми методами, да ещё со своим уникальным арсеналом:
if (document.querySelectorAll) (function(){
var oldSelect = select;
/* ... */
select = function( selector, context, results, seed, xml ) {
// используем querySelectorAll когда нет фильтров в запросе,
// когда это запрос не по xml-объекту,
// и когда не обнаружено проблем с запросом
// ещё есть пара проверок, которые я опустил для наглядности
try {
push.apply(
results,
slice.call(context.querySelectorAll( selector ), 0)
);
return results;
} catch(qsaError) { /* подвёл, опять, ну сколько можно */ }
/* ... */
// выход Sizzle
return oldSelect( selector, context, results, seed, xml );
};
});
Но давайте уже рассмотрим, как Sizzle ищет в DOM, если-таки метод document.querySelectorAll()
споткнулся. Начнём с разбора алгоритма работы на следующем примере:
$("thead > .active, tbody > .active")
Получить первое выражение до запятой: «
thead > .active
»Разбить на кусочки: «
thead
», «>
», «.active
»Найти исходное множество элементов по последнему кусочку (все «
.active
»)Фильтровать справа налево (все «
.active
» которые находятся непосредственно в «thead
»)Искать следующий запрос после запятой (возвращаемся к первому пункту)
Оставить только уникальные элементы в результате
Глядя на данный алгоритм вы должны заметить, что правила читаются справа налево!
Копаем глубже. При анализе даже самого маленького выражения есть несколько нюансов, на которые стоит обратить внимание. Первый – это приоритет поиска, он различен для различных браузеров (в зависимости от их возможностей, или «невозможностей»):
{
order: new RegExp( "ID|TAG" +
(assertUsableName ? "|NAME" : "") +
(assertUsableClassName ? "|CLASS" : "")
)
}
Не обращайте внимание на RegExp – это внутренняя кухня Sizzle.
Таким образом, рассматривая выражение div#my
, Sizzle найдёт вначале элемент с id="my"
, а потом уже проверит на соответствие с <div>
. Хорошо, это вроде как фильтрация, и она тоже соблюдает очерёдность – это второй нюанс:
{
preFilter: {
"ATTR": function (match) { /* ... */ },
"CHILD": function (match) { /* ... */ },
"PSEUDO": function (match) { /* ... */ }
},
filter: {
"ID": function (id) { /* ... */ },
"TAG": function (nodeName) { /* ... */ },
"CLASS": function (className) { /* ... */ },
"ATTR": function (name, operator, check) { /* ... */ },
"CHILD": function (type, argument, first, last) { /* ... */ },
"PSEUDO": function (pseudo, argument, context, xml) { /* ... */ }
}
}
Третий нюанс – это относительные фильтры, которые работают сразу с двумя элементами (они вам знакомы по CSS-селекторам):
{
relative: {
">": { dir: "parentNode", first: true },
" ": { dir: "parentNode" },
"+": { dir: "previousSibling", first: true },
"~": { dir: "previousSibling" }
}
}
Ой, зачем я вас всем этим гружу? Почитайте лучше об оптимизации запросов в следующем главе.
Официальная документация по библиотеке Sizzle доступна на GitHub'е проекта.
Last updated