据我了解,使用element.querySelector()
时,查询应从特定元素开始。
但是,当我使用下面的代码运行时,它会选择特定元素中的第一个DIV
标签。
const rootDiv = document.getElementById('test');
console.log(rootDiv.querySelector('div').innerHTML);
console.log(rootDiv.querySelector('div > div').innerHTML);
console.log(rootDiv.querySelector('div > div > div').innerHTML);
console.log(rootDiv.querySelector('div > div > div > div').innerHTML);
console.log(rootDiv.querySelector('div > div > div > div > div').innerHTML);
<div>
<div>
<div id="test">
<div>
<div>
This is content
</div>
</div>
</div>
</div>
</div>
如您所见,前几个结果是相同的。 这是一个错误吗?还是会从文档开始处查询?
答案 0 :(得分:25)
querySelector
的作用是在文档中的某个地方找到一个与传递的CSS选择器匹配的元素,然后 then 检查所找到的元素是否是后代您调用querySelector
的元素。它不是从被调用的元素开始并向下搜索-而是始终从文档级别开始,查找与选择器匹配的元素,并检查该元素是否也是调用上下文元素的后代。这有点不直观。
所以:
someElement.querySelector(selectorStr)
就像
[...document.querySelectorAll(selectorStr)]
.find(elm => someElement.contains(elm));
一种可能的解决方案是使用:scope
来指示您希望选择从rootDiv
开始而不是从document
开始:
const rootDiv = document.getElementById('test');
console.log(rootDiv.querySelector(':scope > div').innerHTML);
console.log(rootDiv.querySelector(':scope > div > div').innerHTML);
console.log(rootDiv.querySelector(':scope > div > div > div').innerHTML);
<div>
<div>
<div id="test">
<div>
<div>
This is content
</div>
</div>
</div>
</div>
</div>
除Edge之外,所有现代浏览器均支持
:scope
。
答案 1 :(得分:7)
当前接受的答案以某种方式为发生的事情提供了有效的逻辑解释,但实际上是错误的。
Element.querySelector
触发match a selector against tree算法,该算法从根元素开始 ,并检查其后代是否与选择器匹配。
选择器本身是绝对的,它对文档没有任何了解,甚至不需要将Element附加到任何内容。除了:scope
属性外,它也与您调用querySelector
方法的 root 无关。
如果我们想自己重写它,那就更像
const walker = document.createTreeWalker(element, {
NodeFilter.SHOW_ELEMENT,
{ acceptNode: (node) => return node.matches(selector) && NodeFilter.FILTER_ACCEPT }
});
return walker.nextNode();
const rootDiv = document.getElementById('test');
console.log(querySelector(rootDiv, 'div>div').innerHTML);
function querySelector(element, selector) {
const walker = document.createTreeWalker(element,
NodeFilter.SHOW_ELEMENT,
{
acceptNode: (node) => node.matches(selector) && NodeFilter.FILTER_ACCEPT
});
return walker.nextNode();
};
<div>
<div>
<div id="test">
<div>
<div>
This is content
</div>
</div>
</div>
</div>
</div>
最大的区别在于此实现不支持特殊的:scope
选择器。
您可能会认为从文档开始或从根元素开始都是相同的,但是不仅性能有所不同,而且在元素未附加到任何文档的情况下也可以使用此方法
const div = document.createElement('div');
div.insertAdjacentHTML('beforeend', '<div id="test"><div class="bar"></div></div>')
console.log(div.querySelector('div>.bar')); // found
console.log(document.querySelector('div>.bar')); // null
以同样的方式,如果我们只有Document.querySelector,则无法匹配Shadow-DOM中的元素。
答案 2 :(得分:1)
查询选择器div > div > div
仅表示:
找到一个既有父级又有祖父母的div,它们也是一个div。
如果您从 test 的第一个孩子开始并检查选择器,则它为true。这就是为什么只有您的最后一个查询才选择最里面的div的原因,因为它具有第一个谓词(用一个great-grand-grandparent-div查找div),而 test的第一个子对象无法满足。
查询选择器将仅测试后代,但它将评估整个文档范围内的表达式。试想一下,选择器就像检查元素的属性一样,即使您仅查看子元素,它仍然是其父元素的子元素。