Сложные веб-приложения с разметкой являются нормой в наши дни. Такая библиотека, как jQuery, с ее простотой использования, совместимостью с различными браузерами и множеством функций, может очень помочь при манипулировании HTML на лету. Поэтому неудивительно, что многие разработчики предпочитают использовать такую библиотеку, а не нативные API DOM, которые исторически были довольно проблематичными . Хотя различия в браузерах все еще остаются проблемой, DOM сегодня находится в гораздо лучшем состоянии, чем это было 5 или 6 лет назад, когда jQuery действительно начал развиваться.
В этом посте я расскажу и продемонстрирую ряд различных функций DOM, которые можно использовать для манипулирования HTML, с особым акцентом на взаимосвязях родительских, дочерних и родственных узлов.
В заключении я расскажу о поддержке браузеров, но имейте в виду, что такая библиотека, как jQuery, по-прежнему считается хорошим вариантом из-за ошибок и несоответствий в использовании этих родных функций.
Подсчет дочерних узлов
Давайте используем следующий HTML-код, который я рассмотрю в различных формах в этой статье:
<body> <ul id="myList"> <li>Example one</li> <li>Example two</li> <li>Example three</li> <li>Example four</li> <li>Example five</li> <li>Example Six</li> </ul> </body> 
  Если я хочу подсчитать, сколько элементов находится внутри элемента <ul> , я могу сделать это двумя способами. 
 var myList = document.getElementById('myList'); console.log(myList.children.length); // 6 console.log(myList.childElementCount); // 6 
  Журналы в последних двух строках дают один и тот же результат, но каждый со своей техникой.  В первом случае я использую свойство children .  Это свойство только для чтения возвращает коллекцию HTML-элементов внутри запрашиваемого элемента, и я использую свойство length чтобы узнать, сколько их в коллекции. 
  Во втором примере я использую childElementCount , который, на мой взгляд, является более чистым и потенциально более обслуживаемым способом сделать это (другими словами, при повторном рассмотрении этого кода у вас, вероятно, не возникнет проблем с выяснением того, что делает childElementCount ). 
  Интересно, что я также могу использовать childNodes.length (вместо children.length ), но обратите внимание на результат: 
 var myList = document.getElementById('myList'); console.log(myList.childNodes.length); // 13 
  Это возвращает 13 потому что childNodes — это совокупность всех узлов, включая узлы пробелов — так что имейте это в виду, если вам требуется возможность различать все дочерние узлы и все узлы элементов. 
Проверка наличия дочерних узлов
  Я также могу использовать метод hasChildNodes() чтобы проверить, есть ли у данного элемента какие-либо дочерние узлы.  Этот метод возвращает логическое значение, чтобы указать, есть ли у него дочерние узлы или нет: 
 var myList = document.getElementById('myList'); console.log(myList.hasChildNodes()); // true 
Я знаю, что в моем списке есть дочерние узлы, но я могу изменить HTML, чтобы удалить элементы внутри списка, чтобы мой HTML выглядел так:
 <body> <ul id="myList"> </ul> </body> 
  Вот результат, если я снова запускаю hasChildNodes() : 
 console.log(myList.hasChildNodes()); // true 
  Обратите внимание, что это все еще регистрирует true  Хотя список не содержит никаких элементов, он содержит пробелы, которые являются допустимым типом узла.  Этот конкретный метод работает с узлами в целом, а не только с узлами элементов  Чтобы гарантировать, что hasChildNodes() возвращает false , мой HTML должен выглядеть так: 
 <body> <ul id="myList"></ul> </body> 
Теперь я получаю ожидаемый результат из моего журнала:
 console.log(myList.hasChildNodes()); // false 
Конечно, если я знаю, что имею дело с потенциальными пробелами, я мог бы сначала проверить существование дочерних узлов, а затем определить, являются ли какие-либо из них узлами элементов, используя свойство nodeType .
Добавление и удаление дочерних элементов
  Существуют методы, которые мы можем использовать для добавления или удаления узлов из DOM.  Наиболее известным, часто используемым в сочетании с createElement() , является метод appendChild() : 
 var myEl = document.createElement('div'); document.body.appendChild(myEl); 
  В этом случае я создаю <div> с помощью createElement() , а затем добавляю его в тело.  Довольно просто, и, вероятно, вы уже использовали эту технику раньше. 
  Но вместо вставки вновь созданного элемента я мог бы также использовать appendChild() в качестве простого средства перемещения существующего элемента.  Допустим, у меня есть следующий HTML: 
 <div id="c"> <ul id="myList"> <li>Example one</li> <li>Example two</li> <li>Example three</li> <li>Example four</li> <li>Example five</li> <li>Example Six</li> </ul> <p>Example text</p> </div> 
Я могу изменить местоположение списка, используя следующий код:
 var myList = document.getElementById('myList'), container = document.getElementById('c'); container.appendChild(myList); 
В результате DOM будет выглядеть так:
 <div id="c"> <p>Example text</p> <ul id="myList"> <li>Example one</li> <li>Example two</li> <li>Example three</li> <li>Example four</li> <li>Example five</li> <li>Example Six</li> </ul> </div> 
  Обратите внимание, что весь список был удален с его места (над абзацем), а затем добавлен в конец тела, помещая его под абзацем.  Поэтому, хотя вы обычно видите appendChild() используется для добавления элементов, созданных с помощью метода createElement() , это также простой способ переместить существующий элемент в другое место на странице. 
  Я также могу полностью удалить ребенка из DOM, используя removeChild() .  Вот как я могу удалить тот же элемент списка из предыдущего фрагмента HTML: 
 var myList = document.getElementById('myList'), container = document.getElementById('c'); container.removeChild(myList); 
  Теперь элемент удален.  Метод removeChild() возвращает удаленный элемент, поэтому я могу сохранить его где-нибудь, если мне понадобится сделать это для дальнейшего использования: 
 var myOldChild = document.body.removeChild(myList); document.body.appendChild(myOldChild); 
  Также есть метод ChildNode.remove() , который является более новой функцией в спецификации: 
 var myList = document.getElementById('myList'); myList.remove(); 
Этот метод не возвращает удаленный объект и не работает в Internet Explorer (только Edge). Следует также отметить, что оба эти метода удаления будут нацелены как на текстовые узлы, так и на элементы.
Замена дочерних элементов
Я могу заменить существующего ребенка новым, независимо от того, существует ли этот новый ребенок где-то уже или я создал его с нуля. Вот HTML-код:
 <p id="par">Example Text</p> 
И тогда JavaScript:
 var myPar = document.getElementById('par'), myDiv = document.createElement('div'); myDiv.className = 'example'; myDiv.appendChild(document.createTextNode('New element text')); document.body.replaceChild(myDiv, myPar); 
  Здесь я создаю элемент, createTextNode() текстовый узел с помощью createTextNode() , а затем вставляю его на страницу вместо элемента абзаца.  Предыдущий элемент абзаца затем заменяется следующим HTML: 
 <div class="example">New element text</div> 
  Как видите, метод replaceChild() принимает два аргумента: новый элемент и старый заменяемый элемент. 
Я также могу использовать этот метод для перемещения существующего элемента с его места и размещения его вместо нового. Посмотрите на следующий HTML:
 <p id="par1">Example text 1</p> <p id="par2">Example text 2</p> <p id="par3">Example text 3</p> 
Я могу заменить третий абзац первым абзацем, используя следующий код:
 var myPar1 = document.getElementById('par1'), myPar3 = document.getElementById('par3'); document.body.replaceChild(myPar1, myPar3); 
Теперь сгенерированный DOM будет выглядеть так:
 <p id="par2">Example text 2</p> <p id="par1">Example text 1</p> 
Таргетинг на конкретные дочерние элементы
  Есть несколько разных способов нацеливания на определенные элементы.  Как было показано ранее, я могу использовать коллекцию children или свойство childNodes , а затем работать оттуда.  Но давайте посмотрим несколько других вариантов. 
  firstElementChild и lastElementChild делают именно то, что предлагают их имена: они нацелены на первый и последний дочерние элементы для указанного родителя.  Давайте вернемся к нашему списку HTML: 
 <body> <ul id="myList"> <li>Example one</li> <li>Example two</li> <li>Example three</li> <li>Example four</li> <li>Example five</li> <li>Example Six</li> </ul> </body> 
Я могу настроить таргетинг на первый или последний элементы в этом списке следующим образом:
 var myList = document.getElementById('myList'); console.log(myList.firstElementChild.innerHTML); // "Example one" console.log(myList.lastElementChild.innerHTML); // "Example six" 
  Я также могу использовать свойства previousElementSibling и nextElementSibling если я хочу нацеливать дочерние элементы, отличные от первого или последнего.  Их можно использовать вместе с firstElementChild и lastElementChild : 
 var myList = document.getElementById('myList'); console.log(myList.firstElementChild.nextElementSibling.innerHTML); // "Example two" console.log(myList.lastElementChild.previousElementSibling.innerHTML); // "Example five" 
  Существуют также похожие firstChild , lastChild , previousSibling и nextSibling , но эти свойства относятся ко всем типам узлов, а не только к элементам.  Во многих случаях специфичные для элемента свойства будут более полезными, поскольку они предназначены только для узлов элементов. 
Внедрение контента в DOM
Я уже смотрел на способы добавления элементов в DOM. Давайте рассмотрим аналогичную концепцию и рассмотрим несколько функций, которые дают вам более детальный контроль над тем, куда вы вставляете контент.
  Во-первых, это простой insertBefore() .  Это работает так же, как replaceChild() , принимая два аргумента, и будет работать с вновь созданным или существующим элементом.  Давайте использовать следующий HTML: 
 <div id="c"> <ul id="myList"> <li>Example one</li> <li>Example two</li> <li>Example three</li> <li>Example four</li> <li>Example five</li> <li>Example Six</li> </ul> <p id="par">Example Paragraph</p> </div> 
Обратите внимание на элемент абзаца. Я собираюсь удалить абзац, а затем вставить его перед списком, одним махом:
 var myList = document.getElementById('myList'), container = document.getElementBy('c'), myPar = document.getElementById('par'); container.insertBefore(myPar, myList); 
Полученный HTML-код будет иметь элемент абзаца перед списком, а не после, что является еще одним способом перемещения элемента из существующего места в другое место на странице:
 <div id="c"> <p id="par">Example Paragraph</p> <ul id="myList"> <li>Example one</li> <li>Example two</li> <li>Example three</li> <li>Example four</li> <li>Example five</li> <li>Example Six</li> </ul> </div> 
  Как и в replaceChild() , insertBefore() принимает два аргумента: добавляемый элемент и элемент, перед которым мы хотим добавить элемент, указанный в первом аргументе. 
  Этот метод довольно прост.  Давайте теперь воспользуемся гораздо более мощным методом для уточнения этих вставок: insertAdjacentHTML() . 
 var myEl = document.getElementById('el'); myEl.insertAdjacentHTML('beforebegin', '<p>New element</p>'); 
Приведенный выше код вставит указанную строку содержимого перед целевым элементом (в данном случае перед списком). Второй аргумент — это строка HTML, которую вы хотите вставить (это также может быть просто текст без тегов HTML). Первый аргумент, также представленный в виде строки, должен иметь одно из следующих четырех значений:
- beforebegin — вставляет строку перед указанным элементом
 - ‘afterbegin’ — вставляет строку в указанный элемент перед его первым дочерним элементом.
 - ‘beforeend’ — вставляет строку в указанный элемент после его последнего потомка
 - ‘afterend’ — вставляет строку после указанного элемента
 
  Чтобы было проще понять, что представляет собой каждое из этих значений, посмотрите комментарии HTML в следующем фрагменте.  Предполагая, что #el div является целевым элементом, каждый комментарий указывает, куда HTML- #el будет идти с указанным значением для первого аргумента: 
 <!-- beforebegin --> <div id="el"> <!-- afterbegin --> <p>Some example text</p> <!-- beforeend --> </div> <!-- afterend --> 
Это должно прояснить, что каждое из возможных значений делает со строкой содержимого.
Поддержка браузера
При обсуждении чего-либо в DOM поддержка браузера всегда является важным фактором. Помимо ошибок, ниже приводится краткое описание того, насколько хорошо эти функции поддерживаются.
Следующие функции поддерживаются во всех браузерах , включая старые версии IE:
- ChildNodes
 - hasChildNodes ()
 - AppendChild ()
 - RemoveChild ()
 - replaceChild ()
 - createTextNode ()
 - PreviousSibling
 - NextSibling
 - InsertBefore ()
 - insertAdjacentHTML ()
 
Следующие функции поддерживаются в IE9 + и во всех других браузерах :
Это оставляет только метод Node.remove () , который, как уже упоминалось, не поддерживает Internet Explorer, но был добавлен в новый браузер Microsoft Edge.
В целом, поддержка браузером этих функций DOM превосходна. Конечно, будут ошибки и проблемы с совместимостью, поэтому обязательно проведите тщательное тестирование, если решите интенсивно использовать любой из них.
Вывод
В идеале, языка JavaScript и нативных API-интерфейсов DOM должно быть достаточно, чтобы мы могли создавать вещи легко и кросс-браузерно. В конце концов, я думаю, мы дойдем до этого, но в то же время такие инструменты, как jQuery, будут продолжать удовлетворять эту потребность.
Не стесняйтесь комментировать любые связанные функции, которые вы обнаружили или использовали при попытке манипулировать HTML на лету с помощью собственных сценариев DOM.