#javascript
#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);
<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>
:scope
поддерживается во всех современных браузерах, кроме Edge.
Комментарии:
1.
:scope
поддерживается не всеми, но большинством основных браузеров. В любом случае, спасибо за объяснение, и это должно быть ответом. Спасибо.2.
:scope
не работает с IE и Edge, я думаю, важно указать на это3. Вот таблица совместимости браузера — мы должны сопротивляться еще несколько месяцев, так как Edge внедряет движок Chromium: P
4. Есть ли у вас какие-либо исходники для движка браузера, который действительно работает таким образом? — Запрос имел бы тот же результат, если бы движок запускался с первого div и проверял, соответствует ли он селектору запроса…
Ответ №2:
Принятый в настоящее время ответ каким-то образом дает правильное логическое объяснение того, что происходит, но фактически они неверны.
Element.querySelector
запускает алгоритм сопоставления селектора с деревом, который идет от корневого элемента и проверяет, соответствуют ли его потомки селектору.
Сам селектор является абсолютным, он не имеет никаких знаний о документе и даже не требует, чтобы ваш элемент был добавлен к любому. И, кроме :scope
атрибута, также не имеет значения, с каким корнем вы вызвали querySelector
метод.
Если бы мы хотели переписать его сами, это было бы больше похоже
const walker = document.createTreeWalker(element, {
NodeFilter.SHOW_ELEMENT,
{ acceptNode: (node) => return node.matches(selector) amp;amp; 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) amp;amp; 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
Точно так же сопоставление элементов в Shadow-DOM было бы невозможно, если бы у нас был только Document.querySelector.
Комментарии:
1. Хорошее объяснение — особенно немного об узлах, которые не являются частью документа. Это более четко представляет, что на самом деле делает движок браузера.
Ответ №3:
Селектор запросов div > div > div
означает только:
Найдите div, у которого есть родительский элемент и дедушка, которые оба также являются div.
И если вы начнете с первого дочернего элемента test и проверите селектор, это правда. И это причина, по которой только ваш последний запрос выбирает самый внутренний div, поскольку у него есть первый предикат (найдите div с пра-пра-дедушкой-div), который не выполняется первым дочерним элементом test.
Селектор запросов будет проверять только потомков, но он будет оценивать выражение в области всего документа. Просто представьте селектор, подобный проверке свойств элемента — даже если вы просматриваете только дочерний элемент, он все равно является дочерним элементом своего родителя.