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

Насколько я понимаю, при использовании 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>

Как видите, первые несколько результатов совпадают. Это баг? Или он будет запрашивать с начала документа?

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
34
0
1 704
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Что делает 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 поддерживается не всеми, кроме большинства основных браузеров. В любом случае, спасибо за объяснение, и это должен быть ответ. Спасибо.
Joey Chong 08.04.2019 09:44
:scope не работает с IE и Edge, думаю важно это указать
Cristian Traìna 08.04.2019 09:47
Здесь — это таблица совместимости браузеров — нам нужно сопротивляться еще несколько месяцев, так как Edge внедряет движок Chromium :P
Cristian Traìna 08.04.2019 09:47

Есть ли у вас какие-либо источники для браузерного движка, действительно работающего таким образом? - Запрос будет иметь тот же результат, если движок запустится с первого div и проверит, соответствует ли он селектору запроса...

Falco 08.04.2019 13:17

Селектор запроса 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.

Хорошее объяснение - особенно немного об узлах, не являющихся частью документа. Это более четко представляет, что на самом деле делает движок браузера.

Falco 08.04.2019 15:46

Другие вопросы по теме