Манипулирование деревом DOM, это альфа и омега любого фронтенд-разработчика, а это не возможно без селекторов позволяющих находить HTML элементы. Давайте подробно разберёмся как они работают.

Основных методов селекторов в JavaScript всего 2 и оба они являются методами классов Document и Element:

  • querySelector() - принимает строку с селектором в качестве аргумента и возвращает первое совпадение с ним или null, если ничего не найдено.

  • querySelectorAll() - точно также принимает аргументом, строку с селектором и возвращает все найденные элементы, в виде массива элементов NodeList, с которым можно работать в цикле for или for/of. Если элементы не будут найдены вернёт пустой массив NodeList.

    Именно этим двум методам стоит отдавать предпочтение в своём коде, хотя есть и другие альтернативы. Селекторы используемые в методах, взяты прямиком из CSS, следовательно фронтендерам они будут уже знакомы, а это нехилый плюс. Ниже будут примеры кода, для которых был набросан простенькая HTMLка:

<!DOCTYPE html>  
<html lang="ru">  
<head>  
    <meta charset="UTF-8">  
    <title>Селекторы</title>  
</head>  
<body>  
<a id="link_1" href="section.html">Ссылка на раздел</a>  
<div id="elem_1"></div>  
<div id="elem_2" name="div_2"></div>  
<div id="elem_3" class="link_wrap">  
    <a id="link_2" href="some-page.html">Обычная ссылка</a>  
    <a id="link_3" href="some-page.html">Новости</a>  
</div>  
<div id="elem_4"></div>  
<script src="script/script.js"></script>  
</body>  
</html>

Посмотрим на разницу работы методов с одинаковыми селекторами:

const one = document.querySelector('div'), // Содержит ссылку на <div id="elem_1">
    all = document.querySelectorAll('div') // Содержит NodeList со всеми 4-мя дивами что есть в нашем документе

Для поиска можно использовать сразу несколько селекторов отделяя друг от друга их запятыми:

const one = document.querySelector('#elem_2, a'), // Содержит элемент ссылки <a id="link_1">, так как элемент по этому селектору встречается раньше
	  all = document.querySelectorAll('a, #elem_2')  // Содержит NodeList с 2 элементами: 2 ссылки </a><a> и </a><div id="elem_2"><a>

Тогда методы вернут элементы которые подходят хотя бы под один из указанных селекторов. Благодаря CSS селекторам можно найти элементы по:

  • ID;

  • Классу;

  • Атрибутам;

  • Позиции.

Как упоминалось выше, методы селекторы имеются у двух классов Document и Element и их работа отличается тем что в случае с классом Element, выбираться будут только потомки элемента у которого был вызван метод:

const someDiv = document.querySelector('[id="elem_3"]'),  
    searchInElem = someDiv.querySelector('a'), // Содержит </a><a id="link_2">
    searchInDocument = document.querySelector('a') // // Содержит </a><a id="link_1">

CSS селекторы по псевдолементам:

  • ::first-line;

  • ::first-letter.

не будут работать с querySelector() и querySelectorAll(), так как предназначены для работы с текстовыми узлами, а не с HTML элементами. Некоторые браузеры не реализуют работу с псевдоклассами CSS:

  • :link;

  • :visited.

Делается это из соображений безопасности, так как через них можно следить за хронологий посещений пользователя.

Полезным в работе может оказаться метод matches(), класса Element, который принимает селектор в качестве аргумента и проверяет, соответствует ли элемент данному селектору, если да, то возвращает true.

const someDiv = document.querySelector('#elem_3')

let isDiv = someDiv.matches('div'), // true
	isLink = someDiv.matches('a') // false

Поиск родительских элементов по селектору

Если метод querySelector() класса Element ищет элементы в направлении сверху вниз и работает только с дочерними элементами, то метод closets() позволяет проделать ту-же операцию в обратном направлении и предназначен для работы уже предками элемента. Он точно также принимает селектор в качестве единственного аргумента и возвращает найденного родителя. Если по указанному селектору ничего найти не удастся то вернёт null.

const someLink = document.querySelector('#link_2'),  
    linkParentDiv = someLink.closest('div') // <div id="elem_3">

Устаревшие методы селекторы

Есть ряд методов класса Document, использование которых не запрещено, но все же лучше вместо них применять querySelector() или querySelectorAll(), чтобы не усложнять код:

  • getElementById() - находит элемент по ID;

  • getElementsByTagName() - находит элемент по имени тега;

  • getElementsByName() - находит элемент по атрибуту name;

  • getElementsByClassName() - находит элемент по имени класса.

    Два метода из данного списка:

  • getElementsByTagName();

  • getElementsByClassName().

Реализованы также и в классе Element и позволяют искать его потомков. Все они за исключением getElementById() возвращают NodeList. getElementById() возвращает объект Element.

NodeList возвращаемый устаревшими методами, является активным, то есть изменяет свою длину при появлении/удалении новых элементов, соответствующих указанному селектору.

const elemById = document.getElementById('elem_1'), // <div id="elem_1">
    tags = document.getElementsByTagName('a'), // HTMLCollection со всеми ссылкам 
    elemByName = document.getElementsByName('div_2'), // NodeList с <div id="elem_2">
    elemByClass = document.getElementsByClassName('link_wrap') // HTMLCollection с <div id="elem_3">

Доступ к элементам через свойства

В классе Document реализован ряд свойств для быстрого доступа к элементам HTML:

  • images- все картинки;

  • forms - все формы;

  • links - все ссылки с атрибутом href;

  • all - содержит все HTML элементы, документа. Наиболее устаревшее из всех свойств и его не рекомендуется использовать. Все они ссылаются на объект HTMLCollection, который похож на NodeList, но в качестве индекса может быть использован ID элемента или его имя (атрибут name):

document.links.link_1 // <a id="link_1">

Все данные свойства являются устаревшими и не рекомендованы к использованию. Опять же querySelector() иquerySelectorAll(), наше всё!

Движение по документу относительно элемента

Мы можем использовать элемент как точку отсчёта и двигаться по документу относительно него в любых направлениях. Для этого в классе Element имеются свойства:

  • parentNode - родительский элемент;

  • children - потомков элемента;

  • firstElementChild - первый дочерний элемент. Равен null, если дочерние элементы отсутствуют;

  • lastElementChild - последний дочерний элемент. Равен null, если дочерние элементы отсутствуют;

  • nextElementSibling - ссылается на следующий (справа) соседний элемент. Равен null, если следующий элементы отсутствуют;

  • previousElementSibling - ссылается на предыдущий (слева) соседний элемент. Равен null, если предыдущий элементы отсутствуют.

Все перечисленные выше свойства, будут ссылаться только на HTML элементам и они игнорируют текстовые узлы и комментарии в коде:

const elem = document.querySelector('#elem_3'), 
    parent = elem.parentNode, // <body>
    children = elem.children, // HTMLCollection c двумя ссылками <a id="link_2"> и <a id="link_3"> 
    firsChild = elem.firstElementChild, // <a id="link_2">
    lastChild = elem.lastElementChild, // <a id="link_3"> 
    prevNeighbour = elem.previousElementSibling, // <div id="elem_2">
    nextNeighbour = elem.nextElementSibling // <div id="elem_4">

А вот например свойство childNodes, будет содержать всех потомков элемента, включая комментарии и текст. На практике можно редко встретить ситуацию когда требуется взаимодействовать с тактовыми узлами, ещё реже с комментариями, так что свойства учитывающие их, в практическом коде штука весьма диковинная, тем не менее знать о них нужно.

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

Ещё есть одно информационное свойство childElementCount, хранящее количество потомков.

const elem = document.querySelector('#elem_3'),
	  childrenCount = elem.childElementCount // 2

Выводы

Обратите внимание на разнообразие способов, реализовать работу с HTML элементами в JS, многие из которых дублируют друг, друга. Причём аналогичная ситуация будет всплывать из раза в раз и в других интерфейсах языка и является одной из причин хейта в его сторону. Подобная хаотичность вызвана необходимостью обратной совместимости с фичами из разных вех развития JavaScript и его не простой историей успеха.

Я же в данном материале постарался описать всё практически всё что есть, указав какие фичи можно использовать, а какие допустимы только под угрозой применения к вам насилия. Код в котором встречаются только 2 одобренных метода, будет легко читаться всеми гребцами на галере, а вот использование всего зоопарка принуждает других разработчиков (да и Вас самого пару месяцев спустя), либо держать в голове кучу не особо нужных нюансов, либо идти в гугл или ChatGPT за разъяснениями, что порой раздражает. Плохой разработчик пишет код для себя, хороший для других, помните это :-)

Комментарии (12)


  1. ermouth
    19.01.2025 14:53

    nextElemtSibliting - ссылается на следующий

    Будьте любезны, вычитайте текст. Ошибок очень много: getElementByClassName (пропущена s, Elements), nextElemtSibliting (правильно nextElementSibling), querySelectot и пр. Ну и с запятыми беда.


    1. Pab10
      19.01.2025 14:53

      Да и в заголовке какой-то скрип... JavaScrip!

      постарался описать всё что есть

      Боюсь тут и 10% нет :) Как минимум следовало упомянуть xpath, добавить ссылки на спецификации или хотя бы MDN для тех, кто хочет получить максимум информации.

      Короче для факультатива 11 класса годится, но не более.


      1. JastaFly Автор
        19.01.2025 14:53

        Как минимум следовало упомянуть xpath

        Возможно Вы правы. Не стал писать про xpath, так как он достаточно сложен и сам по себе тянет на отдельную статью.

        Плюс имхо никогда не встречал xpath в коде фронтендеров, так как он сложно читается и для задач чего-то по проще за глаза хватает. А вот в автотестах без xpath уже никуда, но это уже не фронтенд, потому не стал касаться этой фичи


      1. JastaFly Автор
        19.01.2025 14:53

        Короче для факультатива 11 класса годится, но не более

        Уровень статьи помечен как простой, так что на большее она и не рассчитана)


      1. JastaFly Автор
        19.01.2025 14:53

        Боюсь тут и 10% нет

        А что ещё есть? xpath ну не как на 90% не тянет


        1. Pab10
          19.01.2025 14:53

          https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_selectors это без xpath.
          https://developer.mozilla.org/en-US/docs/Web/XPath

          MDN верно говорит,

          XPath is mainly used in XSLT, but can also be used as a much more powerful way of navigating through the DOM of any XML-like language document using XPathExpression, such as HTML and SVG, instead of relying on the Document.getElementById() or Document.querySelectorAll() methods, the Node.childNodes properties, and other DOM Core features.

          И собственно такого размера абзаца хватило бы, чтобы закрыть вопрос :) В конце концов селекторы нужны для навигации по нодам и все вот эти CSS костыли - это костыли, пусть и удобные. xpath создан как раз с целью навигации по XML документам любого типа, коими HTML-документы так же являются.


          Я не ставлю оценку статье или комментариям, просто надеюсь, что следующие стати будут лучше, спасибо, что пытаетесь. Прошу прощения за духоту :)


          1. JastaFly Автор
            19.01.2025 14:53

            https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_selectors это без xpath.https://developer.mozilla.org/en-US/docs/Web/XPath

            Ну всё-же xpath и псевдоклассы не тянут на 90%, а скорее где-то на 20%

            спасибо, что пытаетесь

            Вам спасибо за действительно объективные и полезные комментарии)


    1. JastaFly Автор
      19.01.2025 14:53

      Спасибо за замечания) Пробежался по тексту исправил опечатки


  1. gishab
    19.01.2025 14:53

    Опять скрипит потертый JavaScript )


  1. Metotron0
    19.01.2025 14:53

    Извините за офтопик, вам русский язык — родной?

    Я хотел было послать найденные мной ошибки по ctrl+enter, но прочиталь чуть дальше и понял, что это займёт много времени, поэтому не стал.


    1. JastaFly Автор
      19.01.2025 14:53

      прочиталь чуть дальше и понял, что это займёт много времени

      Ну так отправьте те ошибки что уже успели найти, один фиг какое-то время уже потрачено. Я бы исправил хоть их и был бы Вам искренне признателен. А так чисто желчь излить пришли....


      1. Metotron0
        19.01.2025 14:53

        Я понимаю, когда человек пишет на чужом языке, или просто никогда не читал, а только пользовался языком на слух, поэтому допускает ошибки. Но у вас опечатки даже в названиях методов и атрибутов. Тут у меня два варианта: или у вас дислексия, или вы решили, что "и так схавают, а если найдут ошибки, просто буду их исправлять". Если это второй случай, то получается, что вы взялись за дело, которое даже не собирались сделать хорошо.

        Отправил вам всё, что нашёл, кроме некоторых запятых, в которых я сам не уверен. К примеру, вокруг слова "например". Я бы поставил, но лень погружаться в правила.