Техники работы с DOM: родительские, дочерние и соседние элементы

Техники работы с DOM: родительские, дочерние и соседние элементы

Сложные и тяжелые веб-приложения стали обычными в наши дни. Кроссбраузерные и простые в использовании библиотеки типа jQuery с их широким функционалом могут сильно помочь в манипулировании DOM на лету. Поэтому неудивительно, что многие разработчики использую подобные библиотеки чаще, чем работают с нативным DOM API, с которым было немало проблем. И хотя различия в браузерах по-прежнему остаются проблемой, DOM находится сейчас в лучшей форме, чем 5-6 лет назад, когда jQuery набирал популярность.

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

Подсчет дочерних узлов

Для демонстрации я буду использовать следующую разметку HTML, в течение статьи мы ее несколько раз изменим:

Если я хочу подсчитать, сколько элементов внутри <ul> , я могу сделать это двумя способами.

Как видите, результаты одинаковые, хотя техники используются разные. В первом случае я использую свойство children . Это свойство только для чтения, оно возвращает коллекцию элементов HTML, находящихся внутри запрашиваемого элемента; для подсчета их количества я использую свойство length этой коллекции.

Во втором примере я использую метод childElementCount , который мне кажется более аккуратным и потенциально более поддерживаемым способом (подробнее обсудим это позже, я не думаю, что у вас возникнут проблемы с пониманием того, что он делает).

Я мог бы попытаться использовать childNodes.length (вместо children.length ), но посмотрите на результат:

Он возвращает 13, потому что childNodes это коллекция всех узлов, включая пробелы — учитывайте это, если вам важна разница между дочерними узлами и дочерними узлами-элементами.

Проверка существования дочерних узлов

Для проверки наличия у элемента дочерних узлов я могу использовать метод hasChildNodes() . Метод возвращает логическое значение, сообщающие об их наличии или отсутствии:

Я знаю, что в моем списке есть дочерние узлы, но я могу изменить HTML так, чтобы их не было; теперь разметка выглядит так:

И вот результат нового запуска hasChildNodes() :

Метод по прежнему возвращает true . Хотя список не содержит никаких элементов, в нем есть пробел, являющийся валидным типом узла. Данный метод учитывает все узлы, не только узлы-элементы. Чтобы hasChildNodes() вернул false нам надо еще раз изменить разметку:

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

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

Добавление и удаление дочерних элементов

Есть техника, которые можно использовать для добавления и удаления элементов из DOM. Наиболее известная из них основана на сочетании методов createElement() и appendChild() .

В данном случае я создаю <div> с помощью метода createElement() и затем добавляю его к body . Очень просто и вы наверняка использовали эту технику раньше.

Но вместо вставки специально создаваемого элемента, я также могу использовать appendChild() и просто переместить существующий элемент. Предположим, у нас следующая разметка:

Я могу изменить место расположения списка с помощью следующего кода:

Итоговый DOM будет выглядеть следующим образом:

Обратите внимание, что весь список был удален со своего места (над параграфом) и затем вставлен после него перед закрывающим body . И хотя обычно метод appendChild() используется для добавления элементов созданных с помощью createElement() , он также может использоваться для перемещения существующих элементов.

Я также могу полностью удалить дочерний элемент из DOM с помощью removeChild() . Вот как удаляется наш список из предыдущего примера:

Теперь элемент удален. Метод removeChild() возвращает удаленный элемент и я могу его сохранить на случай, если он потребуется мне позже.

Таке существует метод ChildNode.remove() , относительно недавно добавленный в спецификацию:

Этот метод не возвращает удаленный объект и не работает в IE (только в Edge). И оба метода удаляют текстовые узлы точно так же, как и узлы-элементы.

Замена дочерних элементов

Я могу заменить существующий дочерний элемент новым, независимо от того, существует ли этот новый элемент или я создал его с нуля. Вот разметка:

Здесь я создал элемент, добавил текстовый узел с помощью createTextNode() и вставил его на страницу на место параграфа. Теперь на месте первоначального параграфа следующий HTML:

Как видите, метод replaceChild() принимает два аргумента: новый элемент и заменяемый им старый элемент.

Я также могу использовать это метод для перемещения существующего элемента. Взгляните на следующий HTML:

Я могу заменить третий параграф первым параграфом с помощью следующего кода:

Теперь сгенерированный DOM выглядит так:

Выборка конкретных дочерних элементов

Существует несколько разных способов выбора конкретного элемента. Как показано ранее, я могу начать с использования коллекции children или свойства childNodes . Но взглянем на другие варианты:

Свойства firstElementChild и lastElementChild делают именно то, чего от них можно ожидать по их названию: выбирают первый и последний дочерние элементы. Вернемся к нашей разметке:

Я могу выбрать первый и последний элементы с помощью этих свойств:

Я также могу использовать свойства previousElementSibling и nextElementSibling , если я хочу выбрать дочерние элементы, отличные от первого или последнего. Это делается сочетанием свойств firstElementChild и lastElementChild :

Также есть сходные свойства firstChild , lastChild , previousSibling , и nextSibling , но они учитывают все типы узлов, а не только элементы. Как правило, свойства, учитывающие только узлы-элементы полезнее тех, которые выбирают все узлы.

Вставка контента в DOM

Я уже рассматривал способы вставки элементов в DOM. Давайте перейдем к похожей теме и взглянем на новые возможности по вставке контента.

Во-первых, есть простой метод insertBefore() , он во многом похож на replaceChild() , принимает два аргумента и при этом работает как с новыми элементами, так и с существующими. Вот разметка:

Обратите внимание на параграф, я собираюсь сначала убрать его, а затем вставить перед списком, все одним махом:

В полученном HTML параграф будет перед списком и это еще один способ перенести элемент.

Как и replaceChild() , insertBefore() принимает два аргумента: добавляемый элемент и элемент, перед которым мы хотим его вставить.

Этот метод прост. Попробуем теперь более мощный способ вставки: метод insertAdjacentHTML() .

Этот код вставит определенную строку контента перед целевым элементом (в данном случае перед списком). Второй аргумент это строка с HTML, которую вы хотите вставить (это может быть и простой текст). Первый аргумент, это строка с одним из четырех возможных значений:

  • beforebegin – Вставляет строку перед указанным элементом.
  • afterbegin – Вставляет строку внутри указанного элемента перед первым дочерним элементом.
  • beforeend – Вставляет строку внутри указанного элемента после последнего дочернего элемента
  • afterend – Вставляет строку после указанного элемента

Чтобы было проще понять, как работает каждое из этих значений, взгляните на комментарии в сниппете разметки. Подразумевая, что #el div это целевой элемент, каждый комментарий показывает, где будет находится вставленный HTML.

Этот пример делает понятным, что делает каждое из этих значений.

Поддержка в браузерах

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

Следующие возможности поддерживаются везде, включая старые версии IE:

Эти возможности поддерживаются в IE9+ и других современных браузерах:

И остается метод Node.remove(), который, как уже упоминалось, поддерживается Microsoft только начиная с браузера Edge.

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

Заключение

В идеале, возможностей языка JavaScript и DOM API должно хватать для того, чтобы все делалось просто и кроссбраузерно. В итоге, мы к этому обязательно придем, но пока эти проблемы будут продолжать решаться с помощью инструментов типа jQuery.

📎📎📎📎📎📎📎📎📎📎