Узлы и элементы DOM-дерева в JavaScript

Узлы и элементы DOM-дерева в JavaScript
Содержание:
  1. Типы и имена DOM-узлов
  2. Исследование DOM
  3. Классы DOM-узлов
  4. Комментарии

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

Типы и имена DOM-узлов

Как мы уже знаем структурой DOM является дерево. Оно состоит из связанных друг с другом узлов. Узлы бывают разных типов, в зависимости от того, чему соответствует этот узел в HTML. То есть при преобразовании HTML-текста в DOM, разные сущности в нём преобразуются в разные типы узлов.

Основную структуру DOM-дерева составляют именно узлы, образованные HTML-тегами. Их называют узлами-элементами или просто элементами.

Узнать тип узла в DOM можно с помощью свойства nodeType:

JavaScript
console.log(document.nodeType); // 9
console.log(document.body.nodeType); // 1

Это свойство возвращает число от 1 до 12, обозначающее тип узла.

Основные значения:

  • 1 – элемент (Node.ELEMENT_NODE);
  • 2 – атрибут (Node.ATTRIBUTE_NODE);
  • 3 – текстовый узел (Node.TEXT_NODE);
  • 8 – комментарий (Node.COMMENT_NODE);
  • 9 – document (Node.DOCUMENT_NODE);
  • 10 – узел, содержащий тип документа (Node.DOCUMENT_TYPE_NODE);
  • 11 – узел, представляющий фрагмент документа DocumentFragment (Node.DOCUMENT_FRAGMENT_NODE).

В скобках приведены константы класса Node. Они обычно используются в коде, когда нужно проверить тип DOM-узла в JavaScript. Их намного удобнее использовать, чем запоминать числовые коды:

JavaScript
console.log(document.body.nodeType === Node.DOCUMENT_NODE); // false
console.log(document.body.nodeType === Node.ELEMENT_NODE); // true

Например, получим doctype документа и узнаем его числовой код:

JavaScript
const doctype = document.doctype;
// это ещё один способ как можно получить doctype документа
const doctypeSame = document.childNodes[0];
// получим числовой код узла
console.log(doctype.nodeType); // 10
console.log(doctypeSame.nodeType); // 10

Теперь изучим свойство nodeName. С его помощью мы можем узнать имя узла или тег, если узел является элементом:

JavaScript
console.log(document.body.nodeName); // "BODY"
console.log(document.doctype.nodeName) // "html"
console.log(document.nodeName); // #document"

Свойство nodeName для других узлов, не являющимися элементами возвращает различные значения:

  • для текстовых узлов – "#text";
  • для узлов-комментариев – "#comment";
  • для document"#document" и так далее.

Получить имя тега элемента можно не только с помощью nodeName, но также посредством свойства tagName. tagName запрограммирован в браузере как геттер, он содержится в prototype класса Element. nodeName – это тоже геттер, но находится он в другом месте, в prototype класса Node. Поэтому свойство tagName доступно только для узлов-элементов, и не доступно для других типов узлов.

Прочитать про объекты, прототипы и наследование можно в этой статье.

Исследование DOM

В браузерах при разработке веб-приложений и сайтов имеется очень полезный инструмент DevTools.

Открыть в браузере Chrome его можно через меню или посредством комбинации клавиш:

  • macOSCmd + Shift + I;
  • WindowsCtrl + Shift + I или F12;
  • LinuxCtrl + Shift + I.

На вкладке Element вы можете исследовать DOM и CSS. При необходимости их можно изменять прямо здесь, и смотреть как будут выглядеть эти правки прямо на веб-странице.

Выбрать нужный элемент на веб-странице можно разными способами:

  • кликнуть по нему правой кнопкой мыши и выбрать в открывшемся меню пункт «Inspect» или «Посмотреть код»;
  • найти его в DOM, для поиска элемента дополнительно можно использовать окно поиска, которое можно вызвать с помощью комбинации клавиш Ctrl + F;
  • нажать на значок и визуально выбрать нужный элемент.

После выбора узла мы можем обратиться к нему в консоли через $0. При этом предыдущий выбранный узел будет доступен как $1 и так далее. Это можно использовать при изучении DOM и отладке сайта.

Например, выберем комментарий:

JavaScript
// тип узла
$0.nodeType // 8
// имя узла
$0.nodeName // "#comment"
// значение узла
$0.nodeValue // " Заголовок H1 "
Узнаем у выбранного узла DOM его тип, имя и значение

Свойство nodeValue позволяет получить содержимое текстового узла или комментария. Для остальных узлов оно возвращает в качестве значения null.

С помощью nodeValuee мы можем также установить новое значение этому узлу:

Изменим значение комментария в DOM с помощью свойства nodeValue

Кроме nodeValue нам также доступно свойство data, с помощью которого мы можем выполнить аналогичные действия:

Получим и изменим значение текстового узла в DOM с помощью свойства data

Получить и изменить содержимое элементов осуществляется с помощью других свойств, таких как textContent и innerHTML. Например, выведем значения которые возвращают эти свойства для элемента <ul>:

Получим текстовое и HTML содержимое элемента в DOM соответственно с помощью методов textContent и innerHTML

Здесь мы видим \n и пробелы. \n – это перевод строки. Так как по факту, например, первый <li> расположен не сразу после <ul>, а перед ним имеется вот такой контент. Он при парсинге страницы будет преобразован браузером в текстовый узел DOM. Таким образом, первым дочерним узлом <ul> будет именно этот тестовый узел, и только потом уже <li>. \n образовался из-за того что мы поставили Enter, а четыре пробела – это то количество пробелов, которые мы установили перед тем как написали тег <li>.

Текстовый узел, который был образован в DOM из символов переноса строки и пробелов, расположенных перед li

В DOM пробелы, переводы строк, знаки табуляции и другие символы расположенные между элементами образуют текстовые DOM-узлы.

Например, чтобы их не было в <ul>, его разметка должна быть записана следующим образом:

HTML
<ul><li>Android</li><li>iOS</li></ul>

При выборе DOM-элемента на вкладке Styles будет отображаться весь CSS, применённый к этому элементу, в том числе будет отображены и дефолтные стили браузера. Правила можно редактировать, отключать с помощью чекбоксов и дописывать новые. Все изменения применяются сразу.

Весь CSS, применённый к DOM-элементу, можно увидеть на вкладке Styles в инструментах разработчика

На вкладке Computed мы можем посмотреть результирующие стили, примененные к элементу.

На вкладке Computed в инструментах разработчика можно посмотреть итоговые стили, примененные в выбранному элементу

На вкладке Event Listeners отображаются все обработчики событий, привязанные к данному DOM-элементу.

На вкладке Event Listeners в инструментах разработчика можно посмотреть все обработчики событий, привязанные к выбранному элементу

Классы DOM-узлов

Узлы в DOM являются объектами или другими словами экземплярами определенных классов.

Например, DOM-элемент <body> является экземпляром класса HTMLBodyElement. В этом можно убедиться следующим образом:

JavaScript
document.body.constructor.name // HTMLBodyElement
// или так
document.body.toString() // [object HTMLBodyElement]
// или так
document.body instanceof HTMLBodyElement // true

Таким образом, DOM-узлы в JavaScript являются обычными объектами. С некоторыми свойствами и методами этих объектов мы уже познакомились выше.

JavaScript
// например, установим свойству id значение wrapper
document.body.id = 'wrapper';
// получим тег элемента
document.body.tagName // "BODY"

Например, если выбрать на странице ссылку и получить её класс, то мы увидим, что она является экземпляром HTMLAnchorElement, а не HTMLBodyElement.

JavaScript
$0.constructor.name // HTMLAnchorElement

Экземпляры HTMLAnchorElement в отличие от HTMLBodyElement имеют свои определённые свойства и методы, которых нет у <body>.

В DOM разные элементы могут являются экземплярами разных классов. Но все они в качестве прототипа имеют объект HTMLElement.prototype, то есть значение свойства prototype класса HTMLElement:

Схема, на которой показано что элементы в DOM являются экземплярами разных классов, каждый из которых наследуется от HTMLElement
JavaScript
document.querySelector('body').__proto__ === HTMLBodyElement.prototype; // true
// при наличии элемента <a> на странице
document.querySelector('a').__proto__ === HTMLAnchorElement.prototype; // true
// при наличии элемента <div> на странице
document.querySelector('div').__proto__ === HTMLDivElement.prototype; // true

Таким образом, HTMLElement – это базовый класс, от которого наследуется другие классы, такие как HTMLBodyElement, HTMLAnchorElement, HTMLDivElement и другие. Они в отличие от HTMLElement используются для создания конкретных HTML-элементов.

Но если пойти дальше и рассмотреть класс HTMLElement, то он наследуется от Element.

Кстати, класс Element является основой не только для HTMLElement, но и других классов, например, предназначенных для XML и SVG:

Схема, на которой показано что элементы в DOM являются экземплярами разных классов, каждый из которых наследуется от HTMLElement
JavaScript
document.querySelector('body').__proto__.__proto__.__proto__ === Element.prototype; // true
// при наличии элемента <svg> на странице
document.querySelector('svg').__proto__.__proto__.__proto__.__proto__ === Element.prototype; // true

Ещё выше находится класс Node. Он содержит общие свойства и методы, характерные для всех DOM-узлов.

При этом класс Node не применяется непосредственно для создания объектов. Он применяется для организации наследования. От него наследуется Element и CharacterData. От CharacterData в свою очередь наследуются классы Text и Comment, которую используются соответственно для создания текстовых узлов и комментариев.

Классы, которые наследуются от Node

Если пойти ещё выше, то увидим в цепочке прототипов объект EventTarget.prototype. Класс EventTarget – это корневой класс, благодаря которому все DOM-узлы поддерживают обработку событий. В EventTarget.prototype содержатся такие методы, как, например, addEventListener, dispatchEvent и другие.

После EventTarget идёт уже Object. Object – это класс, который является потомком для всех объектов, которые имеются в JavaScript.

Таким образом, каждый узел в DOM является экземпляром того или иного класса. Набор свойств и методов, который имеет тот или иной узел в DOM определяется не только его классом, но и результатом наследования. Пример наследования классов DOM-узлов приведен на следующей схеме:

Классы, которые наследуются от Node

Следующая тема: Перемещение по элементам и узлам DOM-дерева.

Комментарии: 6

Александр
Александр
Добрый день.

prntscr.com/qbxhu1
Специально создал простую страничку для лучшего понимания DOM, но есть моменты с непредсказуемым результатом. Созданы 2 div-a и по сути div с индексом (0) и div с индексом (1). Внутри каждого 2 элемента с индексами 0 и 1. Почему в первом случае выводится только 0-ой «ребенок» div-а, а во втором случае и 0-ой и 1-вый «ребенок».
Спасибо
Александр Мальцев
Александр Мальцев
Привет!
В примере забыл теги div закрыть:
<div>
  <p class="Hi_01">Привет_1</p>
  <p class="GoodBy_01">Пока_1</p>
</div>
<div>
  <p class="Hi_02">Привет_2</p>
  <p class="GoodBy_02">Пока_2</p>
</div>
Александр
Александр
DOM Inspector сейчас вроде отсутствует в Хроме в таком виде как на картинке!?
Да и не совсем понял суть задания (Измените код HTML документа, представлено на этом уроке таким образом, чтобы в дереве было как можно меньше ненужных текстовых узлов.)
Александр Мальцев
Александр Мальцев
Да, сейчас уже нет такого дополнения.
В этом случае можете использовать этот пример в песочнице.
В этом примере написан код JavaScript, который перебирает DOM узлы документа и выводит их на экран в виде схемы с учетом их вложенности. Все текстовые узлы документа в ней выводятся как #text.
Ваша задача изменить HTML код так, чтобы при выводе не было ненужных текстовых узлов.
Александр
Александр
Я тут экспериментирую с DOM и столкнулся с проблемкой. Не могу вывести список одинаковых элементов prntscr.com/qblhnk там есть 2 span элемента (firstChildren and lastChildren). Как их вытянуть и посчитать их количество?

(код VBA Excel)
2 способа загнать в переменную слитый текст из 2-х переменных и прбела.

sName = a.getElementsByClassName(«text»)(3).FirsChild.textContent _
& " " & a.getElementsByClassName(«text»)(3).LastChild.textContent

sName = a.getElementsByClassName(«text»)(3).Children(0).textContent _
& " " & a.getElementsByClassName(«text»)(3).Children(1).textContent

Кроме FirsChild и LastChild есть еще какие-нибудь Child? Где почитать?

Спасибо
Александр Мальцев
Александр Мальцев
Если в VBA поддерживается метод querySelectorAll, то используйте его:
a.querySelectorAll('div.text > span + span.second').length
Почитать про методы для поиска узлов можно в этой статье, а про методы, основанные на связях — здесь.