有没有办法测试选择器是否匹配给定的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
答案 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
函数执行此操作。
答案 6 :(得分:-3)
只为您的元素使用id? HTML-ID必须是唯一的......