Техники работы с 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.