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'е проекта: