Насколько я понимаю, при использовании element.querySelector()
запрос должен начинаться с определенного элемента.
Однако, когда я запускаю код ниже, он сохраняет выделение первого тега DIV
в конкретном элементе.
const rootDiv = document.getElementById('test');
console.info(rootDiv.querySelector('div').innerHTML);
console.info(rootDiv.querySelector('div > div').innerHTML);
console.info(rootDiv.querySelector('div > div > div').innerHTML);
console.info(rootDiv.querySelector('div > div > div > div').innerHTML);
console.info(rootDiv.querySelector('div > div > div > div > div').innerHTML);
<div>
<div>
<div id = "test">
<div>
<div>
This is content
</div>
</div>
</div>
</div>
</div>
Как видите, первые несколько результатов совпадают. Это баг? Или он будет запрашивать с начала документа?
Что делает querySelector
, так это находит элемент где-то в документе, который соответствует переданному селектору CSS, а тогда проверяет, что найденный элемент является потомком элемента, для которого вы вызвали querySelector
. Он не начинается с элемента, для которого он был вызван, и выполняет поиск вниз — скорее, он всегда начинается на уровне документа, ищет элементы, соответствующие селектору, и проверяет, является ли этот элемент потомком вызывающего элемента контекста. Это немного неинтуитивно.
Так:
someElement.querySelector(selectorStr)
как
[...document.querySelectorAll(selectorStr)]
.find(elm => someElement.contains(elm));
Возможное решение — использовать :scope
, чтобы указать, что вы хотите, чтобы выделение начиналось с rootDiv
, а не с document
:
const rootDiv = document.getElementById('test');
console.info(rootDiv.querySelector(':scope > div').innerHTML);
console.info(rootDiv.querySelector(':scope > div > div').innerHTML);
console.info(rootDiv.querySelector(':scope > div > div > div').innerHTML);
<div>
<div>
<div id = "test">
<div>
<div>
This is content
</div>
</div>
</div>
</div>
</div>
:scope
поддерживается во всех современных браузерах, кроме Edge.
:scope
не работает с IE и Edge, думаю важно это указать
Есть ли у вас какие-либо источники для браузерного движка, действительно работающего таким образом? - Запрос будет иметь тот же результат, если движок запустится с первого div и проверит, соответствует ли он селектору запроса...
Селектор запроса div > div > div
означает только:
Найдите div, у которого есть родитель и прародитель, которые также являются div.
И если вы начнете с первого потомка тестовое задание и проверите селектор, это правда. И это причина, по которой только ваш последний запрос выбирает самый внутренний div, поскольку он имеет первый предикат (найти div с прапрапрадедом-div), который не выполняется первым дочерним элементом тестовое задание.
Селектор запроса будет проверять только потомков, но будет оценивать выражение в рамках всего документа. Просто представьте себе селектор, например, проверяющий свойства элемента — даже если вы просматриваете только дочерний элемент, он все равно является дочерним элементом своего родителя.
Принятый в настоящее время ответ каким-то образом дает правильное логическое объяснение того, что происходит, но на самом деле он ошибочен.
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.info(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.info(div.querySelector('div>.bar')); // found
console.info(document.querySelector('div>.bar')); // null
Точно так же сопоставление элементов в Shadow-DOM было бы невозможно, если бы у нас был только Document.querySelector.
Хорошее объяснение - особенно немного об узлах, не являющихся частью документа. Это более четко представляет, что на самом деле делает движок браузера.
:scope
поддерживается не всеми, кроме большинства основных браузеров. В любом случае, спасибо за объяснение, и это должен быть ответ. Спасибо.