Подтвердить что ты не робот

Проверьте, соответствует ли селектор данному элементу

Есть ли способ проверить, будет ли селектор соответствовать данному элементу DOM? Предпочтительно, без использования внешней библиотеки, такой как Sizzle. Это для библиотеки, и я хотел бы свести к минимуму количество сторонних плагинов, необходимых для "основной" библиотеки. Если это заканчивается тем, что требует Sizzle, я просто добавлю это как плагин в библиотеку для тех, кто хочет включить эту функцию.

Например, я мог бы сделать что-то вроде:

var element = <input name="el" />

matches("input[name=el]", element) == true

EDIT: подумав об этом, я придумал решение, это технически работает, но оно не кажется оптимальным с точки зрения эффективности:

function matchesSelector(selector, element) { 
    var nodeList = document.querySelectorAll(selector); 
    for ( var e in nodeList ) {
        return nodeList[e] === element; 
    }
    return false; 
}

В основном функция запрашивает весь документ с данным селектором, а затем выполняет итерацию над узломList. Если данный элемент находится в nodeList, он возвращает true, а если он не возвращает false.

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

EDIT: Flavius ​​Stef указал мне на решение для браузера для Firefox 3.6+, mozMatchesSelector. Я также нашел эквивалент для Chrome (неизвестность версии неизвестна, и она может работать или не работать в Safari или других браузерах веб-браузера): webkitMatchesSelector, который в основном совпадает с реализацией Firefox. Я еще не нашел встроенной реализации для IE-браузеров.

В приведенном выше примере использование будет:

element.(moz|webkit)MatchesSelector("input[name=el]")

Кажется, W3C также обратился к этому в спецификации Selectors API Level 2 (все еще черновик в данный момент). matchesSelector будет методом на DOM Elements после его одобрения.

W3C Использование: element.matchesSelector(selector)

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

Рамки или библиотеки, реализующие эту функциональность:

http://www.prototypejs.org/api/element/match

http://developer.yahoo.com/yui/docs/YAHOO.util.Selector.html

http://docs.jquery.com/Traversing/is

http://extjs.com/deploy/dev/docs/output/Ext.DomQuery.html#Ext.DomQuery-methods

http://base2.googlecode.com/svn/doc/base2.html#/doc/!base2.DOM.Element.matchesSelector

http://wiki.github.com/jeresig/sizzle/

4b9b3361

Ответ 1

Для тех, кто посещает эту страницу после этих лет, эта функциональность теперь реализована во всех современных браузерах как element.matches без префикса поставщика (кроме ms для браузеров MS, отличных от Edge 15, и webkit для Android/KitKat). См. http://caniuse.com/matchesselector.

Ответ 2

Для лучшей производительности используйте возможности браузера ((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 is выполняется намного быстрее. Первая оптимизация, на которую я хотел бы обратить внимание, - избегать обхода дерева, если нам это не нужно. Для этого вы можете посмотреть на правую часть селектора и проверить, соответствует ли это элементу. Однако будьте осторожны, если селектор на самом деле является несколькими селекторами, разделенными запятыми, тогда вам придется протестировать их. На этом этапе вы создаете парсер селектора CSS, поэтому вы можете также использовать библиотеку.

Ответ 3

В отсутствие xMatchesSelector я собираюсь попробовать добавить стиль с запрошенным селектором к объекту styleSheet, а также некоторые произвольные правила и значения, которые вряд ли будут использоваться. Затем проверьте computed/currentStyle элемента, чтобы узнать, унаследовал ли он добавленное правило CSS. Что-то вроде этого для IE:

function ieMatchesSelector(selector, element) {
  var styleSheet = document.styleSheets[document.styleSheets.length-1];

  //arbitrary value, probably should first check 
  //on the off chance that it is already in use
  var expected = 91929;

  styleSheet.addRule(selector, 'z-index: '+expected+' !important;', -1);

  var result = element.currentStyle.zIndex == expected;

  styleSheet.removeRule(styleSheet.rules.length-1);

  return result;
}

Там, вероятно, есть сумочка, полная getcha с этим методом. Вероятно, лучше всего найти какое-то неявное проприетарное правило CSS, которое с меньшей вероятностью будет иметь визуальный эффект, чем z-index, но поскольку он удален почти сразу после его установки, кратковременное мерцание должно быть единственным побочным эффектом, если это произойдет. Кроме того, более неясное правило будет с меньшей вероятностью переопределяться более конкретным селектором, правилами атрибута стиля или другими важными правилами (если IE даже поддерживает это). Во всяком случае, стоит попробовать хотя бы.

Ответ 5

Сейчас я занимаюсь этой проблемой. Я должен поддерживать IE8 с собственным Javascript, который представляет собой любопытную задачу: IE8 поддерживает как querySelector, так и querySelectorAll, но не соответствуетSelector. Если ваша ситуация схожа, вот вам вариант:

Когда вы передадите DOM node и селектор, сделайте мелкую копию node, а также ее родителя. Это сохранит все их атрибуты, но не сделает копии их соответствующих детей.

Прикрепите клонированный node к клонированному родительскому объекту. Используйте querySelector для клонированного родителя - единственное, что ему нужно для поиска, - это единственный дочерний node, который имеет такой процесс, который является постоянным временем. Он либо вернет дочерний элемент node, либо не будет.

Это выглядит примерно так:

function matchesSelector(node, selector)
{
   var dummyNode = node.cloneNode(false);
   var dummyParent = node.parent.cloneNode(false);
   dummyParent.appendChild(dummyNode);
   return dummyNode === dummyParent.querySelector(selector);
}

Возможно, стоит создать целую цепочку неглубококопированных родителей вплоть до корня node и запросить (в основном пустой) фиктивный корень, если вы хотите проверить свою связь node его предкам.

Сверху моей головы. Я не уверен, какая часть селекторов будет работать, но я думаю  это было бы хорошо для всех, кто не беспокоился о тестируемых node детях. YMMV.

- EDIT -

Я решил написать функцию на мелкой копии всего, начиная с node, проверенной на root. Используя это, можно использовать гораздо больше селекторов. (Тем не менее, ничего не связано с братьями и сестрами.)

function clonedToRoot(node)
{
    dummyNode = node.cloneNode(false);
    if(node.parentNode === document)
    {
        return {'root' : dummyNode, 'leaf' : dummyNode};
    }
    parent = clonedToRoot(node.parentNode).root;
    parent.appendChild(dummyNode);
    return {'root' : parent, 'leaf' : dummyNode};
}

function matchesSelector(node, selector)
{
    testTree = clonedToRoot(node)
    return testTree.leaf === testTree.root.querySelector(selector)
}

Я бы поприветствовал эксперта, чтобы объяснить, какие у него есть селектора, которые не будут охватывать!

Ответ 6

Современные браузеры могут делать это с помощью функции document.querySelectorAll.

http://www.w3.org/TR/selectors-api/

Ответ 7

Просто используйте идентификатор для своего элемента? Идентификаторы HTML должны быть уникальными...