【问题标题】:Test if a selector matches a given element测试选择器是否匹配给定元素
【发布时间】:2011-03-19 07:01:02
【问题描述】:

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

【问题讨论】:

  • 我无法想象你会如何做到这一点——假设你想要支持广泛的选择器,或者所有 CSS2 或 3 选择器——无论如何都不会非常接近重新实现 Sizzle。既然已经优化和调试过了,为什么不直接使用呢?
  • @Pointy:我希望有一种方法可以通过现代浏览器中的内置选择器引擎(通过 querySelector 和 querySelectorAll)“本地”完成。如果没有,就像我说的那样,我将求助于使用库,但将此功能委托给插件,这样我的“核心”库就不需要 Sizzle。
  • Firefox 3.6 有 Node.mozMatchesSelector(): developer.mozilla.org/en/DOM/Node.mozMatchesSelector
  • @Flavius Stef:看起来还没有任何跨浏览器的方法可以做到这一点,但感谢您提供的链接,我按照 W3C 的 2 级规范进行操作,发现他们已经解决了这个问题,所以这只是规范的最终确定和实现它的浏览器的问题。如果您在自己的回复中添加有关在“W3C 选择器 API 级别 2(初稿)”中包含 matchSelector 方法的更多信息,我会将其标记为答案。如果没有,我会将其编辑到我的帖子中。
  • 使用 Modernizr,您可以使用 Modernizr.prefixed('MatchesSelector,element) 来获取具有正确(或没有)供应商前缀的函数(如果存在),然后可以将其用作 fn &amp;&amp; fn(selector)

标签: javascript css-selectors


【解决方案1】:

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

【讨论】:

  • 这是一个图书馆,所以我不知道他们会用它来做什么。在库(表单验证库)的上下文中,仅通过 ID 进行匹配实际上是没有用的。
【解决方案2】:

现代浏览器可以使用document.querySelectorAll 函数来做到这一点。

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

【讨论】:

  • 这不是问题所在。 OP 有一个元素,想知道它是否匹配特定的选择器。
【解决方案3】:

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

【讨论】:

  • 感谢您的回复。我已经在使用 querySelectorAll,但它不允许您选择要测试的元素。它从整个文档中选择。
  • 规范包含这个例子,所以你可能可以像 element.querySelectorAll() 一样使用它: var div = document.getElementById("bar"); var p = div.querySelector("body p");
  • 不幸的是,它只从元素的后代/子元素中选择,而不是元素本身。到目前为止,我唯一能想到的就是在整个文档上运行选择器,并遍历结果以查看我的元素是否在 NodeList 中。不过听起来效率极低。
【解决方案4】:

在没有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 甚至支持的话)。无论如何,至少值得一试。

【讨论】:

  • 有趣的解决方案!你试过这样做吗?它有效吗?
  • 聪明的解决方案,但它只适用于当前附加到文档的元素。
【解决方案5】:

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

当您获得 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。

-- 编辑--

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

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】:

    对于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搜索

    这是一个使用document.querySelectorAllmatchesSelector 函数。

    // 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;
    }
    

    现在我们看到,即使元素位于与 文档。

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

    【讨论】:

      【解决方案7】:

      为了那些多年后访问此页面的人的利益,此功能现在在所有现代浏览器中实现为 element.matches,没有供应商前缀(除了 Edge 15 以外的 MS 浏览器的 ms 和 @987654324 @ 用于 Android/KitKat)。见http://caniuse.com/matchesselector

      【讨论】:

      • 这是一种耻辱
      • 有效。在您的控制台中尝试:document.body.matches('.question-page')
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-11-13
      • 1970-01-01
      • 2015-06-08
      • 2015-06-27
      • 1970-01-01
      • 2011-05-12
      • 2010-11-13
      相关资源
      最近更新 更多