使用querySelector时的怪异行为

Joe*_*ong 32 javascript

据我了解,使用时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);
Run Code Online (Sandbox Code Playgroud)
<div>
  <div>
    <div id="test">
      <div>
        <div>
        This is content
        </div>
      </div>
    </div>
  </div>
</div>
Run Code Online (Sandbox Code Playgroud)

如您所见,前几个结果是相同的。这是一个错误吗?还是会从文档开始查询?

Cer*_*nce 26

什么querySelector做的是找到一个元素在文档中的某个地方的CSS选择器过去了,匹配然后将检查发现的元素是你叫元素的后代querySelector上。它不是从被调用的元素开始并向下搜索-而是始终从文档级别开始,查找与选择器匹配的元素,并检查该元素是否也是调用上下文元素的后代。这有点不直观。

所以:

someElement.querySelector(selectorStr)
Run Code Online (Sandbox Code Playgroud)

就好像

[...document.querySelectorAll(selectorStr)]
  .find(elm => someElement.contains(elm));
Run Code Online (Sandbox Code Playgroud)

一个可能的解决方案是使用:scope来指示您希望选择从而rootDiv不是从开始document

someElement.querySelector(selectorStr)
Run Code Online (Sandbox Code Playgroud)
[...document.querySelectorAll(selectorStr)]
  .find(elm => someElement.contains(elm));
Run Code Online (Sandbox Code Playgroud)

:scope 除Edge之外,所有现代浏览器均支持该功能。


Kai*_*ido 7

当前接受的答案以某种方式对发生的事情提供了有效的逻辑解释,但实际上是错误的。

Element.querySelector触发针对树算法的选择器匹配,该算法从根元素开始,并检查其后代是否与选择器匹配

选择器本身是绝对的,它对文档没有任何了解,甚至不需要将Element追加到任何内容。除了:scope属性之外,它也不关心调用该方法的querySelector

如果我们想自己重写它,那就更像

const walker = document.createTreeWalker(element, {
  NodeFilter.SHOW_ELEMENT,
  { acceptNode: (node) => return node.matches(selector) && NodeFilter.FILTER_ACCEPT }
});
return walker.nextNode();
Run Code Online (Sandbox Code Playgroud)

const walker = document.createTreeWalker(element, {
  NodeFilter.SHOW_ELEMENT,
  { acceptNode: (node) => return node.matches(selector) && NodeFilter.FILTER_ACCEPT }
});
return walker.nextNode();
Run Code Online (Sandbox Code Playgroud)
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();
};
Run Code Online (Sandbox Code Playgroud)

区别在于此实现不支持特殊:scope选择器。

您可能会认为从文档开始或从根元素开始都是相同的,但不仅会在性能方面有所不同,而且还可以在元素未附加到任何文档的情况下使用此方法。

<div>
  <div>
    <div id="test">
      <div>
        <div>
          This is content
        </div>
      </div>
    </div>
  </div>
</div>
Run Code Online (Sandbox Code Playgroud)

以同样的方式,如果我们只有Document.querySelector,则在Shadow-DOM中无法匹配元素。