Для наилучшая производительность , используйте реализации браузера ((moz|webkit|o|ms)matchesSelector
), где это возможно. Когда вы не можете сделать это, вот ручная реализация.
Важным примером для рассмотрения является тестирование селекторов для элементов, не прикрепленных к документу.
Вот подход, который обрабатывает эту ситуацию. Если выясняется, что рассматриваемый element
не прикреплен к документу, ползти вверх по дереву, чтобы найти самого старшего предка (последний ненулевой parentNode
) и поместить его в DocumentFragment
. Затем из этого DocumentFragment
позвоните querySelectorAll
и посмотрите, находится ли ваш element
в результирующем NodeList
.
Вот код.
Документ
Вот структура документа, с которой мы будем работать. Мы возьмем .element
и проверим, соответствует ли он селекторам li
и .container *
.
<!DOCTYPE html>
<html>
<body>
<article class="container">
<section>
<h1>Header 1</h1>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
</section>
<section>
<h1>Header 2</h1>
<ul>
<li>one</li>
<li>two</li>
<li class="element">three</li>
</ul>
</section>
<footer>Footer</footer>
</article>
</body>
</html>
Поиск с помощью document.querySelectorAll
Вот функция matchesSelector
, которая использует document.querySelectorAll
.
// uses document.querySelectorAll
function matchesSelector(selector, element) {
var all = document.querySelectorAll(selector);
for (var i = 0; i < all.length; i++) {
if (all[i] === element) {
return true;
}
}
return false;
}
Это работает, пока этот элемент находится в document
.
// this works because the element is in the document
console.log("Part 1");
var element = document.querySelector(".element");
console.log(matchesSelector("li", element)); // true
console.log(matchesSelector(".container *", element)); // true
Однако произойдет сбой, если элемент будет удален из document
.
// but they don't work if we remove the article from the document
console.log("Part 2");
var article = document.querySelector("article");
article.parentNode.removeChild(article);
console.log(matchesSelector("li", element)); // false
console.log(matchesSelector(".container *", element)); // false
Поиск в пределах DocumentFragment
Исправление требует поиска любого поддерева, в котором находится element
. Вот обновленная функция с именем matchesSelector2
.
// uses a DocumentFragment if element is not attached to the document
function matchesSelector2(selector, element) {
if (document.contains(element)) {
return matchesSelector(selector, element);
}
var node = element;
var root = document.createDocumentFragment();
while (node.parentNode) {
node = node.parentNode;
}
root.appendChild(node);
var all = root.querySelectorAll(selector);
for (var i = 0; i < all.length; i++) {
if (all[i] === element) {
root.removeChild(node);
return true;
}
}
root.removeChild(node);
return false;
}
Теперь мы видим, что matchSelector2 работает, даже если элемент находится в поддереве, отделенном от документа.
// but they will work if we use matchesSelector2
console.log("Part 3");
console.log(matchesSelector2("li", element)); // true
console.log(matchesSelector2(".container *", element)); // true
Это можно увидеть на jsfiddle .
Собираем все вместе
Вот последняя реализация, которую я придумал:
function is(element, selector) {
var node = element;
var result = false;
var root, frag;
// crawl up the tree
while (node.parentNode) {
node = node.parentNode;
}
// root must be either a Document or a DocumentFragment
if (node instanceof Document || node instanceof DocumentFragment) {
root = node;
} else {
root = frag = document.createDocumentFragment();
frag.appendChild(node);
}
// see if selector matches
var matches = root.querySelectorAll(selector);
for (var i = 0; i < matches.length; i++) {
if (this === matches.item(i)) {
result = true;
break;
}
}
// detach from DocumentFragment and return result
while (frag && frag.firstChild) {
frag.removeChild(frag.firstChild);
}
return result;
}
Важным примечанием является то, что реализация jQuery является намного быстрее. Первая оптимизация, на которую я хотел бы обратить внимание, - это избегать ползания по дереву, если нам не нужно Для этого вы можете посмотреть на самую правую часть селектора и проверить, соответствует ли это элементу. Тем не менее, имейте в виду, что если на самом деле селектор состоит из нескольких селекторов, разделенных запятыми, вам придется протестировать каждый из них. На данный момент вы создаете синтаксический анализатор CSS-селекторов, так что вы также можете использовать библиотеку.