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