Манипулирование деревом 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)
Metotron0
19.01.2025 14:53Извините за офтопик, вам русский язык — родной?
Я хотел было послать найденные мной ошибки по ctrl+enter, но прочиталь чуть дальше и понял, что это займёт много времени, поэтому не стал.
JastaFly Автор
19.01.2025 14:53прочиталь чуть дальше и понял, что это займёт много времени
Ну так отправьте те ошибки что уже успели найти, один фиг какое-то время уже потрачено. Я бы исправил хоть их и был бы Вам искренне признателен. А так чисто желчь излить пришли....
Metotron0
19.01.2025 14:53Я понимаю, когда человек пишет на чужом языке, или просто никогда не читал, а только пользовался языком на слух, поэтому допускает ошибки. Но у вас опечатки даже в названиях методов и атрибутов. Тут у меня два варианта: или у вас дислексия, или вы решили, что "и так схавают, а если найдут ошибки, просто буду их исправлять". Если это второй случай, то получается, что вы взялись за дело, которое даже не собирались сделать хорошо.
Отправил вам всё, что нашёл, кроме некоторых запятых, в которых я сам не уверен. К примеру, вокруг слова "например". Я бы поставил, но лень погружаться в правила.
ermouth
Будьте любезны, вычитайте текст. Ошибок очень много: getElementByClassName (пропущена s, Elements), nextElemtSibliting (правильно nextElementSibling), querySelectot и пр. Ну и с запятыми беда.
Pab10
Да и в заголовке какой-то скрип... JavaScrip!
Боюсь тут и 10% нет :) Как минимум следовало упомянуть xpath, добавить ссылки на спецификации или хотя бы MDN для тех, кто хочет получить максимум информации.
Короче для факультатива 11 класса годится, но не более.
JastaFly Автор
Возможно Вы правы. Не стал писать про xpath, так как он достаточно сложен и сам по себе тянет на отдельную статью.
Плюс имхо никогда не встречал
xpath
в коде фронтендеров, так как он сложно читается и для задач чего-то по проще за глаза хватает. А вот в автотестах безxpath
уже никуда, но это уже не фронтенд, потому не стал касаться этой фичиJastaFly Автор
Уровень статьи помечен как простой, так что на большее она и не рассчитана)
JastaFly Автор
А что ещё есть? xpath ну не как на 90% не тянет
Pab10
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_selectors это без xpath.
https://developer.mozilla.org/en-US/docs/Web/XPath
MDN верно говорит,
И собственно такого размера абзаца хватило бы, чтобы закрыть вопрос :) В конце концов селекторы нужны для навигации по нодам и все вот эти CSS костыли - это костыли, пусть и удобные. xpath создан как раз с целью навигации по XML документам любого типа, коими HTML-документы так же являются.
Я не ставлю оценку статье или комментариям, просто надеюсь, что следующие стати будут лучше, спасибо, что пытаетесь. Прошу прощения за духоту :)
JastaFly Автор
Ну всё-же xpath и псевдоклассы не тянут на 90%, а скорее где-то на 20%
Вам спасибо за действительно объективные и полезные комментарии)
JastaFly Автор
Спасибо за замечания) Пробежался по тексту исправил опечатки