Странное поведение при использовании querySelector

Насколько я понимаю, при использовании element.querySelector(), запрос должен быть 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>

Ответы

Ответ 1

Что делает querySelector, так это находит где-то в документе элемент, соответствующий переданному селектору CSS, и затем проверяет, является ли найденный элемент потомком элемента, для которого вы вызвали 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>

Ответ 2

В настоящее время принятый ответ каким-то образом дает правильное логическое объяснение того, что происходит, но на самом деле они ошибочны.

Element.querySelector запускает сопоставление селектора с алгоритмом дерева, который идет от корневого элемента и проверяет, соответствуют ли его потомки селектору.

Сам селектор является абсолютным, он не знает никаких документов и даже не требует, чтобы ваш элемент был добавлен к любому. И кроме атрибута :scope не имеет значения, с каким корнем вы вызвали метод querySelector.

Если бы мы хотели переписать это сами, это было бы больше похоже на

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>

Ответ 3

Селектор запросов div > div > div означает только:

Найдите div, у которого есть родитель и родитель, которые также являются div.

И если вы начинаете с первого ребенка теста и проверяете селектор, это правда. И это причина, почему только ваш последний запрос выбирает самый внутренний div, так как он имеет первый предикат (найдите div с div-great-great-grandparent-div), который не выполняется первым потомком test.

Селектор запросов будет проверять только потомков, но он будет оценивать выражение в объеме всего документа. Просто представьте себе селектор, например, проверяющий свойства элемента - даже если вы просматриваете только дочерний элемент, он все еще является дочерним по отношению к его родительскому элементу.