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

IT技术 javascript css-selectors
2021-01-21 11:17:18

有什么方法可以测试选择器是否匹配给定的 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 元素的方法。

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/

6个回答

为了这些年来访问此页面的人的利益,此功能现在在所有现代浏览器中实现,element.matches没有供应商前缀(msEdge 15以外的 MS 浏览器和webkitAndroid/KitKat 除外)。请参阅http://caniuse.com/matchesselector

这是边缘的耻辱
2021-03-21 11:17:18
作品。在您的控制台中尝试:document.body.matches('.question-page')
2021-03-30 11:17:18

为获得最佳性能,请(moz|webkit|o|ms)matchesSelector尽可能使用浏览器实现 ( )。当你不能这样做时,这里是一个手动实现。

要考虑的一个重要情况是测试未附加到文档的元素的选择器。

这是处理这种情况的方法。如果事实证明有element问题的 没有附加到文档,则爬上树以找到最高的祖先(最后一个非 null 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;
}

现在我们看到,即使元素位于与文档分离的子树中,matchesSelector2 也能工作。

// 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 选择器解析器,因此您不妨使用一个库。

在没有 的情况下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,但由于它在设置后几乎立即被删除,如果那样的话,短暂的闪烁应该是唯一的副作用。此外,更模糊的规则不太可能被更具体的选择器、样式属性规则或其他 !important 规则覆盖(如果 IE 甚至支持)。无论如何,至少值得一试。

聪明的解决方案,但它仅适用于当前附加到文档的元素。
2021-03-24 11:17:18
有趣的解决方案!你试过这样做吗?它有效地工作吗?
2021-03-27 11:17:18

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

该规范包含此示例,因此您可能可以像 element.querySelectorAll() 一样使用它: var div = document.getElementById("bar"); var p = div.querySelector("body p");
2021-03-17 11:17:18
谢谢回复。我已经在使用 querySelectorAll,但它不允许您选择要测试的元素。它从整个文档中进行选择。
2021-04-06 11:17:18
不幸的是,这只从元素的后代/子元素中选择,而不是元素本身。到目前为止,我唯一能想到的就是在整个文档上运行选择器,并遍历结果以查看我的元素是否在 NodeList 中。虽然听起来非常低效。
2021-04-08 11:17:18

我现在正在处理这个问题。我必须用原生 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);
}

如果您希望能够测试您的节点与其祖先的关系,则可能值得创建一个完整的浅复制父级一直到根节点并查询(大部分为空)虚拟根。

在我的头顶上,我不确定这对选择器的哪个部分有用,但我认为它对任何不担心测试节点的子节点的人都很好。天啊。

- 编辑 -

我决定编写函数来浅复制从被测试节点到根节点的所有内容。使用它,可以使用更多的选择器。(不过,与兄弟姐妹无关。)

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)
}

我欢迎专家解释哪些类型的选择器,这不会涵盖!