测试选择器是否与给定元素匹配

时间:2010-07-21 23:18:08

标签: javascript css-selectors

有没有办法测试选择器是否匹配给定的DOM元素?优选地,不使用像Sizzle这样的外部库。这是一个库,我想尽量减少“核心”库所需的第三方插件数量。如果最终需要Sizzle,我只需将其作为插件添加到库中,以便那些想要启用它的功能。

例如,我可以做类似的事情:

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

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

编辑:在考虑了更多之后,我提出了一个解决方案,这在技术上有效,但在效率方面似乎不是最佳的:

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

基本上,函数使用给定的选择器查询整个文档,然后迭代nodeList。如果给定元素在nodeList中,则返回true,如果不是,则返回false。

如果有人能提出更有效的答案,我很乐意将答案标记为答案。

编辑Flavius Stef向我指出了针对Firefox 3.6 +的浏览器特定解决方案mozMatchesSelector。我还发现Chrome的等效版本(版本兼容性未知,它可能在Safari或其他webkit浏览器上有效或不可用):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/

7 个答案:

答案 0 :(得分:32)

为了多年后访问此页面的用户的好处,此功能现在在所有现代浏览器中实现为element.matches,没有供应商前缀(除了ms以外的MS浏览器而不是Edge 15 ,和webkit for Android / KitKat)。请参阅http://caniuse.com/matchesselector

答案 1 :(得分:8)

对于best performance,尽可能使用浏览器实现((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的{​​{1}}函数。

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

现在我们看到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;
}

您可以在jsfiddle处看到此工作。

全部放在一起

这是我提出的最终实施:

// but they will work if we use matchesSelector2
console.log("Part 3");
console.log(matchesSelector2("li", element)); // true
console.log(matchesSelector2(".container *", element)); // true

一个重要的注意事项是jQuery的is实现要快得多。我要研究的第一个优化是,如果我们不必要,就要避免爬树。为此,您可以查看选择器的最右侧部分并测试它是否与元素匹配。但是,请注意,如果选择器实际上是由逗号分隔的多个选择器,那么您将必须测试每个选择器。此时您正在构建一个CSS选择器解析器,因此您也可以使用库。

答案 2 :(得分:5)

在没有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;
}

这个方法可能有一个装满了手提包的手提包。可能最好找到一些不太明显的专有CSS规则,这种规则不太可能具有z-index的视觉效果,但由于它几乎在设置后立即被移除,如果有这种情况,短暂的闪烁应该是唯一的副作用。另外一个更加模糊的规则将不太可能被更具体的选择器,样式属性规则或其他重要规则覆盖(如果IE甚至支持这一规则)。无论如何,至少值得一试。

答案 3 :(得分:3)

W3C选择器API(http://www.w3.org/TR/selectors-api/)指定document.querySelectorAll()。并非所有浏览器都支持此功能,因此您必须谷歌支持它的那些浏览器:http://www.google.com/search?q=browsers+implementing+selector+api

答案 4 :(得分:1)

我现在正在处理这个问题。我必须使用本机Javascript支持IE8,这提出了一个奇怪的挑战:IE8支持querySelector和querySelectorAll,但不支持matchesSelector。如果您的情况类似,可以考虑以下选项:

当您交付DOM节点和选择器时,请为该节点及其父节点创建一个浅表副本。这将保留其所有属性,但不会复制其各自的子项。

将克隆的节点附加到克隆的父节点。在克隆的父级上使用querySelector - 它唯一需要搜索的是它拥有的唯一子节点,因此这个过程是常量时间。它将返回子节点,否则不会。

看起来像这样:

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

如果您希望能够测试节点与其祖先的关系,那么可能值得创建一个完整的浅拷贝父类链,一直到根节点并查询(通常为空)虚拟根。

在我的脑海中,我不确定这部分选择器会起作用,但我认为  对于任何不担心测试节点的孩子的人来说,它都能做得很好。 YMMV。

- 编辑 -

我决定将该函数编写为浅层复制从被测节点到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)
}

我欢迎专家解释哪些选择器不会覆盖!

答案 5 :(得分:0)

现代浏览器可以使用document.querySelectorAll函数执行此操作。

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

答案 6 :(得分:-3)

只为您的元素使用id? HTML-ID必须是唯一的......