javascript

Селекторы HTML элементов в JavaScrip

  • понедельник, 20 января 2025 г. в 00:00:06
https://habr.com/ru/articles/874744/

Манипулирование деревом 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. не будут работать с querySelectot() и querySelectotAll(), так как предназначены для работы с текстовыми узлами, а не с 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, использование которых не запрещено, но все-же лучше вместо них применять querySelectot() или querySelectotAll(), чтобы не усложнять код:

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

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

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

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

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

  • getElementByTagName();

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

Реализованы так-же и в классе 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">

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

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

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

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

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

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

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

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

  • previousElemtSibliting - ссылается на предыдущий (слева) соседний элемент. Равен 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, будет содержать всех потомков элемента, включая комментарии и текст. На практике можно редко встретить ситуацию когда требуется взаимодействовать с тактовыми узлами, ещё реже с комментариями, так что свойства учитывающие, в практическом коде штука весьма диковинная, но тем не менее знать о них нужно.

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

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

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

Выводы

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

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