Статьи

Советы и методы DOM: родитель, ребенок и братья и сестры

Сложные веб-приложения с разметкой являются нормой в наши дни. Такая библиотека, как 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:

Следующие функции поддерживаются в IE9 + и во всех других браузерах :

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

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

Вывод

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

Не стесняйтесь комментировать любые связанные функции, которые вы обнаружили или использовали при попытке манипулировать HTML на лету с помощью собственных сценариев DOM.