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