3.4. DOM — объектная модель документа

DOM была придумана создателями браузеров. Консорциум World Wide Web Consortium (W3C) стандартизировал DOM как единую спецификацию.

W3C определяет DOM как «интерфейс, не зависящий от платформы и языка, который позволяет программам и сценариям динамически получать доступ к содержимому и структуре документов и обновлять их». То есть, данная модель не предоставляет конкретную реализацию, а лишь описывает объекты, свойства, методы.

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

Когда пользователь запрашивает определенную веб-страницу, механизм рендеринга начинает получать HTML, CSS и JS-файлы запрошенной веб-страницы через сетевой уровень. После получения содержимого страницы, HTML и CSS-файлы анализируются по частям. Узлы DOM создаются для формирования дерева DOM и дерева CSSOM.

Дерево визуализации создается путем объединения деревьев DOM и CSSOM. Дерево визуализации содержит как информацию о порядке элементов, так и информацию об их стиле. На этом этапе каждому узлу присваиваются специальные координаты, чтобы гарантировать, что каждый элемент отображается в одном и том же положении на экране. После чего выполняется обход дерева визуализации, и каждый узел, упомянутый в дереве, отображается на экране.

1. Введение в DOM

DOM — это API для HTML и XML-документов. API расшифровывается как интерфейс прикладного программирования, который можно определить как совокупность способов связи между различными компонентами программного обеспечения. Другими словами, API позволяет одной программе взаимодействовать с другой.

Спецификация DOM определяет набор интерфейсов для доступа и управления объектами HTML и XML-документов. Интерфейсы являются абстракцией, средствами определения способа доступа и управления внутренними представлениями документа в приложении.

Согласно терминологии W3C DOM, каждый документ представлен в виде дерева, а каждый автономный элемент или текстовый блок известен как узел — фундаментальный строительный блок. Термин «узел» происходит из сети, где он используется для обозначения точки подключения, и сама сеть представляет собой набор узлов. Узлы имеют отношения родитель-потомок, когда один контейнер содержит другой. Узел — это общее имя для объекта любого типа в иерархии DOM.

Когда происходит загрузка соответствующей страницы, в памяти браузера реализуется поддержка структуры объектов, сгенерированных согласно использованным в документе HTML-тегам. У каждого узла есть родительский узел (за исключением корневого узла document) и любое количество дочерних узлов. Одни типы узлов могут иметь дочерние узлы различных типов, другие являются так называемыми листовыми узлами, у которых не может дочерних узлов. Дерево состоит из узлов, но только некоторые из них являются HTML-элементами.

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

2. Интерфейсы и узлы DOM

Ядро DOM API состоит из Node, Element, Document и других относительно универсальных интерфейсов. Стандарт DOM также включает интерфейсы, специфичные как для HTML-документов, так и тегов многих HTML-элементов. Эти интерфейсы, например, HTMLBodyElement, HTMLTitleElement, обычно определяют набор свойств, отражающих атрибуты тега, которые обеспечивают удобный доступ к значениям атрибутов.

Базовый интерфейс, от которого наследуются все интерфейсы HTML-элементов и который должен использоваться элементами, не имеющими дополнительных атрибутов, — это интерфейс HTMLElement.

Таблица 1. Список интерфейсов для HTML-элементов
Элемент(ы) Интерфейс(ы)
<a> HTMLAnchorElement : HTMLElement
<abbr> HTMLElement
<address> HTMLElement
<area> HTMLAreaElement : HTMLElement
<article> HTMLElement
<aside> HTMLElement
<audio> HTMLAudioElement : HTMLMediaElement : HTMLElement
<b> HTMLElement
<base> HTMLBaseElement : HTMLElement
<bdi> HTMLElement
<bdo> HTMLElement
<blockquote> HTMLQuoteElement : HTMLElement
<body> HTMLBodyElement : HTMLElement
<br> HTMLBRElement : HTMLElement
<button> HTMLButtonElement : HTMLElement
<canvas> HTMLCanvasElement : HTMLElement
<caption> HTMLTableCaptionElement : HTMLElement
<cite> HTMLElement
<code> HTMLElement
<col> HTMLTableColElement : HTMLElement
<colgroup> HTMLTableColElement : HTMLElement
<data> HTMLDataElement : HTMLElement
<datalist> HTMLDataListElement : HTMLElement
<dd> HTMLElement
<del> HTMLModElement : HTMLElement
<details> HTMLDetailsElement : HTMLElement
<dfn> HTMLElement
<dialog> HTMLDialogElement : HTMLElement
<div> HTMLDivElement : HTMLElement
<dl> HTMLDListElement : HTMLElement
<dt> HTMLElement
<em> HTMLElement
<embed> HTMLEmbedElement : HTMLElement
<fieldset> HTMLFieldSetElement : HTMLElement
<figcaption> HTMLElement
<figure> HTMLElement
<footer> HTMLElement
<form> HTMLFormElement : HTMLElement
<h1><h6> HTMLHeadingElement : HTMLElement
<head> HTMLHeadElement : HTMLElement
<header> HTMLElement
<hgroup> HTMLElement
<hr> HTMLHRElement : HTMLElement
<html> HTMLHtmlElement : HTMLElement
<i> HTMLElement
<iframe> HTMLIFrameElemen : HTMLElement
<img> HTMLImageElement : HTMLElement
<input> HTMLInputElement : HTMLElement
<ins> HTMLModElement : HTMLElement
<kbd> HTMLElement
<label> HTMLLabelElement : HTMLElement
<legend> HTMLLegendElement : HTMLElement
<li> HTMLLIElement : HTMLElement
<link> HTMLLinkElement : HTMLElement
<main> HTMLElement
<map> HTMLMapElement : HTMLElement
<mark> HTMLElement
<menu> HTMLMenuElement : HTMLElement
<meta> HTMLMetaElement : HTMLElement
<meter> HTMLMeterElement : HTMLElement
<nav> HTMLElement
<noscript> HTMLElement
<object> HTMLObjectElement : HTMLElement
<ol> HTMLOListElement : HTMLElement
<optgroup> HTMLOptGroupElement : HTMLElement
<option> HTMLOptionElement : HTMLElement
<output> HTMLOutputElement : HTMLElement
<p> HTMLParagraphElement : HTMLElement
<param> HTMLParamElement : HTMLElement
<picture> HTMLPictureElement : HTMLElement
<pre> HTMLPreElement : HTMLElement
<progress> HTMLProgressElement : HTMLElement
<q> HTMLQuoteElement : HTMLElement
<rp> HTMLElement
<rt> HTMLElement
<ruby> HTMLElement
<s> HTMLElement
<samp> HTMLElement
<script> HTMLScriptElement : HTMLElement
<section> HTMLElement
<select> HTMLSelectElement : HTMLElement
<slot> HTMLSlotElement : HTMLElement
<small> HTMLElement
<source> HTMLSourceElement : HTMLElement
<span> HTMLSpanElement : HTMLElement
<strong> HTMLElement
<style> HTMLStyleElement : HTMLElement
<sub> HTMLElement
<summary> HTMLElement
<sup> HTMLElement
<table> HTMLTableElement : HTMLElement
<tbody> HTMLTableSectionElement : HTMLElement
<td> HTMLTableCellElement : HTMLElement
<template> HTMLTemplateElement : HTMLElement
<textarea> HTMLTextAreaElement : HTMLElement
<tfoot> HTMLTableSectionElement : HTMLElement
<th> HTMLTableCellElement : HTMLElement
<thead> HTMLTableSectionElement : HTMLElement
<time> HTMLTimeElement : HTMLElement
<title> HTMLTitleElement : HTMLElement
<tr> HTMLTableRowElement : HTMLElement
<track> HTMLTrackElement : HTMLElement
<u> HTMLElement
<ul> HTMLUListElement : HTMLElement
<var> HTMLElement
<video> HTMLVideoElement : HTMLMediaElement : HTMLElement
<wbr> HTMLElement

Интерфейс Node (который в свою очередь реализует интерфейс EventTarget) является основным типом данных для всей объектной модели документа. Node представляет собой абстрактный интерфейс и не существует как узел. Он используется всеми узлами — Document, DocumentType, DocumentFragment, Element, Text, Attr, CDATASection, ProcessingInstruction и Comment.

Хотя все объекты, реализующие интерфейс Node, предоставляют методы для работы с потомками, не все объекты, реализующие интерфейс Node, могут иметь потомков (например, текстовые узлы).

3. Свойства узлов

Все объекты узлов, например, Element, Text и т.д., наследуют свойства и методы от интерфейса Node. Эти свойства и методы являются базовыми значениями и функциями для управления, проверки и обхода DOM.

Многие свойства DOM доступны только для чтения. Это означает, что их можно использовать для получения информации, но не для ее установки или обновления.

3.1. Определение типа и имени узла: свойства nodeName и nodeType

У объектов Node есть свойства идентификации, такие как nodeName и nodeType.

Таблица 2. Типы и имена узлов
Интерфейс nodeType Значение nodeName
Element Node.ELEMENT_NODE (1) полное имя HTML-тега в верхнем регистре
Attr Node.ATTRIBUTE_NODE (2) полное имя атрибута
Text Node.TEXT_NODE (3) #text
CDATASection Node.CDATA_SECTION_NODE (4) #cdata-section
ProcessingInstruction Node.PROCESSING_INSTRUCTION_NODE (7) имя, определяющее приложение, для которого предназначена инструкция
Comment Node.COMMENT_NODE (8) #comment
Document Node.DOCUMENT_NODE (9) #document
DocumentType Node.DOCUMENT_TYPE_NODE (10) название типа документа
DocumentFragment Node.DOCUMENT_FRAGMENT_NODE (11) #document-fragment
<!DOCTYPE html>
<html lang="ru">
<body>
   <p class="example">Какой-то текст</a>
</body>
</html>

Свойство nodeName возвращает имя/название в виде строки, либо постоянное значение (которому предшествует символ #). Используется только для чтения.

console.log(document.doctype.nodeName)); // html
console.log(document.nodeName); // #document
console.log(document.body.nodeName); // BODY
console.log(document.createDocumentFragment().nodeName); // #document-fragment
console.log(document.querySelector('p').nodeName); // P
console.log(document.querySelector('p').firstChild.nodeName); // #text  
console.log(document.querySelector('p').nodeType === 1); // true

Свойство nodeType возвращает целочисленный идентификатор типа узла. Интерфейс Node содержит константы, которые соответствуют этим числам. Определение типа узла полезно в сценариях, когда перебирается неизвестный набор узлов. Определение типа узла, для которого вы можете написать сценарий, может быть очень удобным, если вы хотите знать, какие свойства и методы доступны для сценария узла.

console.log(document.doctype.nodeType); // 10
console.log(document.nodeType); // 9
console.log(document.createDocumentFragment().nodeType); // 11
console.log(document.querySelector('p').nodeType); // 1
console.log(document.querySelector('p').firstChild.nodeType);// 3

3.2. Получение значения узла: свойство nodeValue

Свойство nodeValue возвращает или устанавливает значение узла.

Для большинства типов узлов возвращает null и любой набор операций игнорируется. Для узлов типа TEXT_NODE, COMMENT_NODE, CDATA_SECTION_NODE и PROCESSING_INSTRUCTION_NODE, значение соответствует текстовым данным, содержащимся в объекте. Для узла ATTRIBUTE_NODE вернет значение атрибута.

Использование данного свойства полезно для извлечения текстовых строк из узлов Text и Comment.

console.log(document.doctype.nodeValue); // null
console.log(document.nodeValue); // null
console.log(document.createDocumentFragment().nodeValue); // null
console.log(document.querySelector('p').nodeValue); // null
console.log(document.querySelector('p').firstChild.nodeValue); // Какой-то текст

3.3. Отношения между узлами

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

<ul id="list">
   <li>Пункт 1</li>
   <li>Пункт 2</li>
   <li><a href="https://google.com">Пункт 3</a></li>
   <li>Пункт 4</li>
</ul>

3.3.1. Свойство childNodes

Каждый узел имеет свойство childNodes, которое извлекает все дочерние узлы объекта и сохраняет их возвращает живой NodeList, содержащий всех потомков данного узла. Свойство используется только для чтения. Живой NodeList означает то, что если потомки узла изменяются, объект NodeList автоматически обновляется. NodeList — это объект типа массива, используемый для хранения упорядоченного списка узлов, доступных по позиции. NodeList не является экземпляром Array. Вы можете получить доступ к дочернему элементу текущего элемента через счетчик массива или метод item().

const elem = document.getElementById('list');
const count = elem.childNodes.length;
const firstChild = elem.childNodes.item(0);
console.log(count); // 9 
console.log(firstChild); // [object Text]

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

3.3.2. Свойство parentNode

Каждый узел имеет свойство parentNode, указывающее на его родителя в дереве документа. Свойство возвращает ссылку на внешний узел, который отражается как объект, принадлежащий документу. Все узлы, содержащиеся в списке дочерних узлов, имеют одного и того же родителя, поэтому каждое из их свойств родительского узла указывает на один и тот же узел. Если нет такого узла, по причине того, что узел находится вверху дерева или не относится к дереву, данное свойство вернет null. Свойство parentNode текстового фрагмента — это внешний узел или элемент, охватывающий этот фрагмент.

const elem = document.getElementById('list');
const parentElem = elem.parentNode;
document.write(parentElem); // [object HTMLBodyElement]

3.3.3. Свойства previousSibling и nextSibling

Можно переходить от одного узла в списке к другому, используя свойства previousSibling и nextSibling. Свойства используются только для чтения.

Свойство previousSibling возвращает узел, представляющий предыдущий одноуровневый узел в дереве или null, если не такого узла.

Свойство nextSibling возвращает узел, представляющий следующий одноуровневый узел в дереве или null, если не такого узла.

Первый узел в списке имеет значение null для его свойства previousSibling, а последний узел в списке имеет значение null для его свойства nextSibling.

const list = document.querySelector('ul');
console.log(list.nextSibling.nodeName); // #text
console.log(list.previousSibling.nodeName); // #text

3.3.4. Свойства firstChild и lastChild

Свойства firstChild и lastChild возвращают узел, представляющий первый или последний потомок узла в дереве или null, если узел не имеет потомков. Свойства используются только для чтения. Они полезны, когда вы хотите вставить новый дочерний элемент до или после всех остальных, и вам нужна точка отсчета для методов, которые добавляют элементы в список узлов документа.

<p id="p1"></p>
<p id="p2"> </p>
console.log(document.getElementById('p1').firstChild); // null
console.log(document.getElementById('p2').lastChild.nodeName); // #text

3.3.5. Свойство parentElement

Свойство parentElement возвращает элемент, который является родителем данного узла. Если узел не имеет родителя или если родитель не элемент, это свойство вернет null. Свойство работает только с элементами HTML, которые отражаются как объекты документа, тогда как узел не обязательно является элементом HTML (например, атрибутом или текстовым фрагментом). Используется только для чтения.

<ul id="list">
   <li>Пункт 1</li>
   <li>Пункт 2</li>
   <li><a href="https://google.com">Пункт 3</a></li>
   <li>Пункт 4</li>
</ul>
<p id="result"></p>
const parentEl = document.getElementById('list').parentElement;
document.getElementById('result').innerHTML = 'Объект: ' + parentEl + '
' + ' Имя узла:' + ' ' + parentEl.nodeName;
Фигура 1. Результат выполнения кода

3.3.6. Свойство ownerDocument

Свойство ownerDocument возвращает документ, в контексте которого узел был создан. Если нет связанного с ним документа, возвращает null. Свойство ownerDocument объекта предоставляет способ создания ссылок на другие объекты в том же документе или для доступа к свойствам и методам объекты документа, обеспечивая быстрый доступ к узлу документа без необходимости обхода иерархии узлов до самого верха. Свойство полезно при определении, к какому документу принадлежит элемент <iframe>, новое окно, вкладка или XML-документ. Используется только для чтения.

<iframe src="https://player.vimeo.com/video/159120552?byline=0&portrait=0&badge=0" width="640" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
const frame = document.querySelector('iframe');
console.log(frame.ownerDocument.location.href);

3.4. Другие свойства узлов: baseURI, isConnected и textContent

Свойство baseURI

Свойство baseURI возвращает базовый URL-адрес документа. В HTML это соответствует протоколу, доменному имени и структуре каталогов, все до последнего /. Это свойство удобно в приложениях, которые импортируют данные XML, и в этом случае источник элемента XML, вероятно, отличается от страницы HTML, на которой он обрабатывается. Используется только для чтения.

<p id="result"></p>
const pageURI = document.baseURI;
document.getElementById('result').innerHTML = 'Базовый URL страницы: ' + pageURI;

Фигура 2. Результат выполнения кода

Свойство isConnected

Свойство isConnected возвращает логическое значение, указывающее, подключен ли узел (напрямую или косвенно) к объекту контекста, например объекту Document в случае обычного DOM или ShadowRoot в случае теневого DOM. Используется только для чтения.

const test = document.createElement('p');
console.log(test.isConnected); // false
document.body.appendChild(test);
console.log(test.isConnected); // true

Свойство textContent

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

<p id="result"></p>
<p id="text">Текст</p>
document.getElementById('result').textContent = 'Какой-то текст';
const text = document.getElementById('text').textContent;
console.log(text); // Текст

4. Манипуляции узлами

4.1. Создание новых узлов

4.1.1. Метод appendChild()

Метод appendChild() добавляет узел (или несколько узлов) в конец списка дочерних узлов родительского узла, на котором вызывается метод. Если дочерних узлов нет, узел добавится в качестве первого дочернего узла.

<p>Привет</p>
const elementNode = document.createElement('b');
const textNode = document.createTextNode(' мир!');
//добавим эти узлы к DOM
document.querySelector('p').appendChild(elementNode);
document.querySelector('b').appendChild(textNode);
Фигура 3. Результат выполнения кода

4.1.2. Метод cloneNode()

Метод cloneNode() позволяет дублировать один узел или узел и все его дочерние узлы. Принимает один логический аргумент, указывающий, следует ли выполнять глубокое копирование. Когда аргумент равен true, клонируется узел и все его поддерево; когда false, клонируется только начальный узел. Возвращаемый клонированный узел принадлежит документу, но ему не назначен родительский узел. Таким образом, клонированный узел не существует в документе до тех пор, пока не будет добавлен с помощью appendChild(), insertBefore() или replaceChild().

При клонировании узла элемента также клонируются все его атрибуты и их значения (включая встроенные события). Все, что добавлено с помощью addEventListener() или node.onclick, не клонируется.

<ul id="menu">
   <li>Главная</li>
   <li>Услуги</li>
   <li>О нас</li>
   <li>Контакты</li>
</ul>
const menu = document.querySelector('#menu').cloneNode(true);
menu.setAttribute('id', 'menu-mobile');    
document.body.appendChild(menu);
Фигура 4. Результат выполнения кода

4.1.3. Метод insertBefore()

Метод insertBefore() вставляет узел перед другим узлом в качестве дочернего узла родительского узла. Принимает два параметра, указанные через запятую, — узел для вставки и узел, перед которым будет вставлен новый узел. Если у родительского узла нет дочерних узлов, то после вставки узел будет единственным дочерним узлом.

<ul id="menu">
   <li>Услуги</li>
   <li>О нас</li>
   <li>Контакты</li>
</ul>
const menu = document.getElementById('menu');
// создаем новый узел li 
const li = document.createElement('li');
li.textContent = 'Главная';
// вставляем новый узел перед первым элементом списка 
menu.insertBefore(li, menu.firstElementChild);

4.1.4. Метод replaceChild()

Метод replaceChild() заменяет один узел другим. Метод принимает два аргумента: узел для вставки и узел для замены. Когда узел вставляется с помощью replaceChild(), все его указатели отношений дублируются из узла, который он заменяет. Несмотря на то, что замененный узел технически по-прежнему принадлежит тому же документу, он больше не имеет определенного местоположения в документе.

<div id="div-1">Элемент 1</div>
<div id="div-2">Элемент 2</div>
//замена узла элемента
const divFirst = document.getElementById('div-1');
var newP = document.createElement('p');
newP.textContent = 'Новый элемент 1';
divFirst.parentNode.replaceChild(newP, divFirst);
//замена текстового узла
const divSecond = document.getElementById('div-2').firstChild;
const divText = document.createTextNode('Элемент 2 новый текст');
divSecond.parentNode.replaceChild(divText, divSecond);
Фигура 5. Результат выполнения кода

4.1.5. Метод removeChild()

Метод removeChild() удаляет дочерний узел из родительского узла. Удаление происходит в несколько этапов: сначала нужно выбрать узел, который хотите удалить; затем получить доступ к его родительскому элементу, как правило, с помощью свойства parentNode. После чего вызвать метод removeChild(), передавая ему ссылку на удаляемый узел.
Удалённый дочерний узел остаётся в памяти, но больше не является частью DOM. Повторно использовать удалённый узел можно с помощью ссылки на объект — oldChild.

//удаление узла элемента без указания его родительского узла
const divFirst = document.getElementById('div-1');     
divFirst.parentNode.removeChild(divFirst);
//удаление текстового узла
const divSecond = document.getElementById('div-2').firstChild;      
divSecond.parentNode.removeChild(divSecond);

4.2. Проверка позиции узла в дереве DOM: методы compareDocumentPosition() и contains()

Метод compareDocumentPosition()

Метод compareDocumentPosition() определяет связь между двумя узлами и возвращает битовую маску, указывающую на связь. Значения битовой маски показаны в следующей таблице. Узел может иметь более одного типа отношений с другим узлом. Например, когда узел одновременно содержит (16) и предшествует (4), возвращаемое значение из compareDocumentPosition() будет равно 20.

Числовой код Описание
1 DOCUMENT_POSITION_DISCONNECTED
Означает, что два узла не принадлежат одному и тому же документу.
2 DOCUMENT_POSITION_PRECEDING
Означает, что переданный узел предшествует выбранному узлу.
4 DOCUMENT_POSITION_FOLLOWING
Означает, что переданный узел следует за выбранным узлом.
8 DOCUMENT_POSITION_CONTAINS
Означает, что переданный узел является предком выбранного узла.
16 DOCUMENT_POSITION_CONTAINED_BY
Означает, что переданный узел является потомком выбранного узла.
32 DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
Означает, что два узла не имеют отношения друг к другу или являются двумя атрибутами одного и того же элемента.
<p id="p1">Первый параграф</p>
<p id="p2">Второй параграф</p>
const p2Position = p1.compareDocumentPosition(p2); // p1 - узел, который сравнивается, p2 - узел, с которым идёт сравнение
console.log(p2Position); // 4, то есть p2 идет за p1

Метод contains()

Метод contains() позволяет узнать, является ли данный узел потомком другого. Метод вызывается для узла-предка, с которого должен начинаться поиск, и принимает единственный аргумент, который является предполагаемым узлом-потомком. Метод возвращает значение true, если узел является потомком данного узла, в противном случае возвращает false.

<body>
   <header></header>
   <main></main>
   <footer></footer>
   <script></script>
 </body>
const footerNode = document.querySelector('footer');
const scriptNode = document.querySelector('script');
console.log(footerNode.contains(scriptNode)); // false

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

<body class="post-template-default single single-post single-format-standard"></body>
const bodyClass = document.body;
console.log(bodyClass.classList.contains('post-template-default')); // true

4.3. Определение идентичности узлов с помощью метода isEqualNode()

Метод isEqualNode() проверяет равенство двух узлов. Два узла равны, если выполняются следующие условия:

  • Узлы одного типа;
  • Атрибуты nodeName, localName, namespaceURI, prefix и nodeValue нулевые или имеют одинаковую длину и идентичны посимвольно;
  • Атрибуты NamedNodeMaps равны нулю или имеют одинаковую длину, и для каждого узла, существующего в одной карте, есть узел, который существует в другой карте и является равным, хотя и не обязательно с тем же индексом.
  • Списки узлов дочерних узлов равны, то есть они оба нулевые или имеют одинаковую длину и содержат одинаковые узлы с одним и тем же индексом. Рекомендуется нормализовать узлы перед сравнением.
<ul>
   <li><a href="">Ссылка</a></li>
   <li><a href="">Ссылка</a></li>
</ul>

<p>Параграф 1</p>
<p>Параграф 2</p>
const listItem = document.querySelectorAll('li');
console.log(listItem[0].isEqualNode(listItem[1])); // true

const p = document.querySelectorAll('p');
console.log(p[0].isEqualNode(p[1])); // false

4.4. Определение отношений между узлами: методы hasChildNodes() и getRootNode()

Метод hasChildNodes()

Метод hasChildNodes() возвращает true, если узел имеет один или несколько дочерних узлов, и false в противном случае. Этот способ более эффективный, чем запрос childNodes.length.

<ul id="list">
   <li>Пункт 1</li>
   <li>Пункт 2</li>
   <li><a href="https://google.com">Пункт 3</a></li>
   <li>Пункт 4</li>
</ul>
const listItems = document.getElementById("list");
if ( listItems.hasChildNodes() ) {
  listItems.removeChild( listItems.firstElementChild );
}

Метод getRootNode()

Метод getRootNode() возвращает корневой узел. Вызов для элемента внутри стандартной веб-страницы вернет объект HTMLDocument, представляющий всю страницу.
Вызов для элемента внутри теневого DOM вернет связанный с ним ShadowRoot.

<div class="js-parent">
   <div class="js-child"></div>
</div>
<div class="js-shadowHost"></div>
const parent = document.querySelector('.js-parent'),
   child = document.querySelector('.js-child'),
   shadowHost = document.querySelector('.js-shadowHost');

console.log(parent.getRootNode().nodeName); // #document
console.log(child.getRootNode().nodeName); // #document

// создадим теневое дерево
const shadowRoot = shadowHost.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = '
Содержимое теневого дерева
'; const shadowChild = shadowRoot.querySelector('.js-shadowChild'); console.log(shadowChild.getRootNode() === shadowRoot); // true console.log(shadowChild.getRootNode({ composed: false }) === shadowRoot); // true console.log(shadowChild.getRootNode({ composed: true }).nodeName); // #document

5. Пространства имен XML: методы lookupNamespaceURI(), isDefaultNamespace() и lookupPrefix()

Пространства имен XML позволяют размещать в одном документе элементы из разных языков, основанных на XML, не опасаясь конфликтов имен элементов. Пространства имен объявляются с использованием семейства зарезервированных атрибутов. Имя такого атрибута должно быть либо xmlns, либо иметь xmlns: в качестве префикса.

Описанные ниже методы в первую очередь полезны, когда у вас есть ссылка на узел, но вы не знаете его связи с остальной частью документа.

<svg xmlns:svg="http://www.w3.org/2000/svg" height="1"></svg>

Метод lookupNamespaceURI()

Метод lookupNamespaceURI() принимает префикс в качестве параметра и возвращает пространство имен, представленное префиксом (prefix), или пространство имен по умолчанию, если аргумент prefix является пустой строкой.

Когда метод вызывается с префиксом в качестве аргумента, он должен возвращать URI пространства имен для данного префикса. Когда метод lookupNamespaceURI() вызывается с пустой строкой в качестве аргумента (представляющего пространство имен по умолчанию), он должен выполнить одно из следующих действий:

Если существует пространство имен по умолчанию, возвращает URI пространства имен по умолчанию. Если пространства имен по умолчанию нет, возвращает либо пустую строку, либо null, либо, если это разрешено привязкой языка, — undefined.

const svgElem = document.getElementsByTagName('svg')[0];
console.log(svgElem.lookupNamespaceURI("")); // http://www.w3.org/2000/svg

Метод isDefaultNamespace()

Метод isDefaultNamespace() принимает URI пространства имен в качестве аргумента. Он возвращает логическое значение, которое равно true, если пространство имен является пространством имен по умолчанию на данном узле, и false в противном случае. Пространство имен HTML-элемента по умолчанию всегда равно "". Для элемента SVG он задается атрибутом xmlns.

const svgElem = document.getElementsByTagName('svg')[0];
console.log(svgElem.isDefaultNamespace("")); // false
console.log(svgElem.isDefaultNamespace("http://www.w3.org/2000/svg")); // true

Метод lookupPrefix()

Метод lookupPrefix() возвращает строку, содержащую префикс для данного URI пространства имен, если он присутствует, и null в противном случае. Если с префиксом пространства имен связано более одного префикса, возвращаемый префикс пространства имен зависит от реализации.

const svgElem = document.getElementsByTagName('svg')[0];
console.log(svgElem.lookupPrefix("http://www.w3.org/2000/svg")); // null

6. Объединение родственных текстовых узлов в один с помощью метода normalize()

Метод normalize() используется для объединения родственных текстовых узлов в один текстовый узел. Родственные текстовые узлы обычно встречаются тогда, когда текст программно добавляется в DOM.

<div></div>
const pElem = document.createElement('p');
const text1 = document.createTextNode('Привет');
const text2 = document.createTextNode(' мир!');
pElem.appendChild(text1);
pElem.appendChild(text2);
document.querySelector('div').appendChild(pElem);
console.log(document.querySelector('p').childNodes.length); // 2
document.querySelector('div').normalize(); 
console.log(document.querySelector('p').childNodes.length); // 1

По материалам DOM Level 3, DOM Level 4, Node

Поделиться: