W3docs

JavaScript: поиск элементов — getElement*, querySelector*

Изучаем методы поиска элементов в DOM: getElementById, getElementsByClassName, querySelector и querySelectorAll — с примерами и объяснением разницы между живыми и статическими коллекциями.

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

  • Устаревшие методы getElement*getElementById, getElementsByClassName, getElementsByTagName. Они быстры и возвращают живые коллекции.
  • Современные методы querySelector*querySelector, querySelectorAll. Они принимают любой CSS-селектор и возвращают статические результаты.

В этом руководстве рассматриваются оба семейства, вспомогательные методы matches, closest и contains для проверки и обхода дерева, а также самый важный подводный камень: разница между живой коллекцией и статической. Каждый пример можно запустить и сразу увидеть результат.

Когда вы научитесь находить элементы, следующими шагами обычно становятся обход DOM для перехода к соседним элементам и манипуляции с DOM для его изменения. Для общего знакомства смотрите раздел выбор элементов DOM.

Эффективный доступ к элементам: getElementById

Метод getElementById — самый быстрый и надёжный способ получить один элемент, поскольку идентификатор (ID) должен быть уникальным в документе, а браузеры индексируют их внутренне. Он возвращает найденный элемент или null, если элемента с таким ID не существует — поэтому перед использованием результата необходимо проверять его на null. Обратите внимание, что вы передаёте голый ID, без ведущего символа # (он используется только в CSS-селекторах). В примере ниже исходный текст «Default text» сразу же заменяется.

<!-- snippet: html-result -->

<!DOCTYPE html>
<html>
<head>
    <title>getElementById Example</title>
</head>
<body>
    <div id="main-content">Default text</div>
    <script>
        const element = document.getElementById('main-content');
        element.innerHTML = "Modified text!"
    </script>
</body>
</html>

Доступ к нескольким элементам: getElementsByClassName и getElementsByTagName

Информация

При выборе элементов по имени класса или имени тега вы получаете обратно HTMLCollection. Это живая коллекция: она автоматически обновляется при изменении DOM. Она похожа на массив — можно обращаться к элементам по индексу (els[0]) и проверять els.length — но это не настоящий массив, поэтому у неё нет forEach, map или filter. Для безопасного перебора сначала преобразуйте её с помощью Array.from(...) (или оператора расширения [...els]).

Пример с getElementsByClassName

Для доступа к нескольким элементам с одним и тем же классом используйте getElementsByClassName. В этом примере у нас есть два элемента div с одинаковым именем класса. Мы изменяем оба, выбирая их по имени класса.

<!-- snippet: html-result -->

<!DOCTYPE html>
<html>
<head>
    <title>getElementsByClassName Example</title>
</head>
<body>
    <div class="info">First Info</div>
    <div class="info">Second Info</div>
    <script>
        const infoElements = document.getElementsByClassName('info');
        Array.from(infoElements).forEach(el => el.innerHTML = "MODIFIED!");
    </script>
</body>
</html>

Пример с getElementsByTagName

Получайте элементы по имени тега с помощью getElementsByTagName. Это полностью аналогично предыдущему примеру, но на этот раз мы выбираем по имени тега, а не по имени класса.

<!-- snippet: html-result -->

<!DOCTYPE html>
<html>
<head>
    <title>getElementsByTagName Example</title>
</head>
<body>
    <p>First Paragraph</p>
    <p>Second Paragraph</p>
    <script>
        const paragraphs = document.getElementsByTagName('p');
        Array.from(paragraphs).forEach(el => el.innerHTML = "MODIFIED!");
    </script>
</body>
</html>

Гибкий поиск с querySelector и querySelectorAll

Выбор с помощью querySelector

Используйте querySelector для поиска первого элемента, соответствующего CSS-селектору. В этом примере мы выбираем первый элемент с классом text, который является прямым потомком элемента с id main.

<!-- snippet: html-result -->

<!DOCTYPE html>
<html>
<head>
    <title>QuerySelector Example</title>
</head>
<body>
    <div id="main"><span class="text">This will be replaced</span></div>
    <div id="other"><span class="text">This one doesn't change</span></div>
    <script>
        const spanInsideDiv = document.querySelector('#main > .text');
        spanInsideDiv.innerHTML = "MODIFIED!";
    </script>
</body>
</html>

Получение нескольких элементов с querySelectorAll

querySelectorAll возвращает все элементы, соответствующие CSS-селектору, в виде статического NodeList. Удобно то, что у NodeList есть встроенный forEach, поэтому можно перебирать его напрямую, без преобразования в массив.

Слово статический важно: querySelectorAll делает снимок совпадающих элементов в момент вызова. Если вы добавите или удалите совпадающие элементы после этого, снимок не изменится. Это прямая противоположность живой HTMLCollection, возвращаемой методами getElementsBy*.

<!-- snippet: html-result -->

<!DOCTYPE html>
<html>
<head>
    <title>QuerySelectorAll Example</title>
</head>
<body>
    <ul>
        <li class="item">Item 1</li>
        <li class="item">Item 2</li>
    </ul>
    <script>
        const items = document.querySelectorAll('.item');
        items.forEach(item => item.innerHTML = "MODIFIED!");
    </script>
</body>
</html>

Живые и статические коллекции: главный подводный камень

Это ловушка, в которую попадает большинство новичков. Живая HTMLCollection отражает текущее состояние DOM при каждом обращении, тогда как статический NodeList заморожен в момент выборки. Фрагмент ниже показывает, как оба реагируют на добавление нового элемента:

// Suppose the page has two <li class="item"> elements.
const live = document.getElementsByClassName('item');   // live HTMLCollection
const snapshot = document.querySelectorAll('.item');     // static NodeList

console.log(live.length);     // 2
console.log(snapshot.length); // 2

// Now add a third matching element.
const li = document.createElement('li');
li.className = 'item';
document.querySelector('ul').appendChild(li);

console.log(live.length);     // 3  — updated automatically
console.log(snapshot.length); // 2  — still the old snapshot

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

Проверка и обход: matches, closest и contains

Поиск — это не только нахождение элементов; зачастую у вас уже есть элемент и нужно задать о нём вопрос.

  • element.matches(selector) возвращает true, если сам элемент соответствует CSS-селектору. Отлично подходит для делегирования событий.
  • element.closest(selector) поднимается вверх по дереву от элемента (включая его самого) и возвращает ближайшего предка, соответствующего селектору, или null.
  • parent.contains(node) возвращает true, если node является самим родителем или его потомком.

<!-- snippet: html-result -->

<!DOCTYPE html>
<html>
<body>
    <section class="card">
        <button id="save" class="btn primary">Save</button>
    </section>
    <div id="out"></div>
    <script>
        const btn = document.getElementById('save');
        const section = document.querySelector('.card');
        const out = document.getElementById('out');

        out.innerHTML =
            'matches(".primary"): ' + btn.matches('.primary') + '<br>' +
            'closest(".card") is section: ' + (btn.closest('.card') === section) + '<br>' +
            'section.contains(btn): ' + section.contains(btn);
    </script>
</body>
</html>

Какой метод использовать?

  • Нужен один элемент по ID? Используйте getElementById — это самый быстрый и понятный способ.
  • Нужен CSS-селектор (потомки, комбинаторы, атрибуты, :not())? Используйте querySelector / querySelectorAll.
  • Нужен живой список, отслеживающий изменения DOM? Используйте getElementsByClassName / getElementsByTagName.
  • Есть элемент и нужно проверить или подняться по дереву? Используйте matches, closest или contains.

Заключение

Поиск элементов — основа любого скрипта для работы с DOM. Используйте getElementById для единственного ID, querySelector* для гибкости CSS-селекторов, а методы getElementsBy* — только тогда, когда вам действительно нужна живая коллекция. Главное — помните разницу между живыми и статическими коллекциями, чтобы они не застали вас врасплох в середине цикла. Далее переходите к разделам обход DOM и манипуляции с DOM.

Практика

Практика
Какие из следующих утверждений верны относительно методов querySelector и getElementById в JavaScript?
Какие из следующих утверждений верны относительно методов querySelector и getElementById в JavaScript?
Was this page helpful?