Навигация по DOM-дереву JS: DOM API

Знакомство с DOM деревом проще всего начать с изучения структуры этого дерева. Если коротко, то DOM-дерево, состоит из узлов (нод, node), которые вместе образуют иерархию, аналогичную HTML. Часть узлов, при этом, являются листовыми, то есть не содержат внутри себя других узлов (детей), а часть внутренними – у них есть дети. Конкретные узлы, чаще всего, описывают собой конкретные теги из HTML и содержат их атрибуты внутри себя. У узлов есть тип, который определяет набор свойств и методов узла. Ниже мы с ними познакомимся.

Корневой элемент в DOM-дереве соответствует тегу <html>. Доступ к нему можно получить так:

const html = document.documentElement;
// Свойство tagName узла содержит имя тега в верхнем регистре
console.log(html.tagName); // => 'HTML'

// Содержимое тега HTML в виде узлов DOM-дерева.
// Текст тоже представлен узлом
html.childNodes; // [head, text, body]

// Потому что head выше body
html.firstChild; // <head>...</head>
html.lastChild; // <body>...</body>

// Второй ребенок. Обращение по индексу.
html.childNodes[1]; // #text

Корневой узел DOM-дерева

Из-за того, что <body> и <head> всегда присутствуют внутри документа, их вынесли на уровень объекта document для более простого доступа:

document.head;
document.body;

По дереву можно не только спускаться, но и подниматься:

// Родитель body это html
document.documentElement === document.body.parentNode; // true
document.body === document.body.childNodes[2].parentNode; // true

Итого, если представить дерево, то по нему можно двигаться как наверх к родительским, вниз к дочерним, так и вбок (влево и вправо) к сестринским (или братским). Картинка ниже это демонстрирует:

Отношения узлов в DOM-дереве

childNodes

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

  1. Это свойство доступно только для чтения. Попытка что-то записать в конкретный элемент не приведет к успеху:

    // Ошибки не будет, но ничего не поменяется
    document.body.childNodes[0] = 'hey';
    

    Изменение DOM дерева осуществляется специальным набором методов, которые будут рассмотрены в соответствующем уроке.

  2. Хотя childNodes и возвращает набор элементов, это всё же не массив. В нём отсутствуют привычные методы, такие как map(), filter() и другие. Но, зато есть forEach():

    // Тип NodeList
    const nodes = document.documentElement.childNodes;
    
    nodes.forEach((el) => console.log(el));
    

    Если очень хочется, то его можно преобразовать в массив и затем уже работать привычным способом:

    Array.from(nodes).map(/*...*/);
    
    

Иерархия

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

DOM Tree

// Самый простой способ посмотреть тип
document.body.toString(); // "[object HTMLBodyElement]"
document.body instanceof HTMLBodyElement; // true

Узлы с типами Text и Comment являются листовыми, то есть они не могут иметь детей. А вот элементы (производные типы от Element) — это то, с чем приходится иметь дело чаще всего. К элементам относятся все типы, представленные тегами в HTML.

При работе с деревом естественным образом возникают понятия дети и потомки. Применительно к DOM-дереву это означает, что тег, у которого есть содержимое (тело), имеет детей и потомков. Чем они отличаются друг от друга?

const html = `
  <html>
    <head></head>
    <body>
      <div id="parent-div">
        <h1>Заголовок</h1>
        Привет!
        <div class="child-div">
          <span>Какой-то <b>текст</b></span>
          <ol>
            <li>1</li>
            <li>2</li>
          </ol>
          <!-- End List -->
        </div>
      </div>
    </body>
  </html>
`;

Тег <div> (с id parent-div) содержит внутри себя три дочерних узла и 14 потомков. Почему?

Дочерние узлы: <h1>, текст "Привет!" и <div> (с классом child-div). Дочерними (по отношению к узлу) являются только те узлы, которые непосредственно в нём лежат (находятся на "первом уровне вложенности").

Потомками (по отношению к узлу) являются все вложенные в него узлы (находящиеся на "всех уровнях вложенности"). Потомками тега <div> (с id parent-div), помимо вышеупомянутых трёх дочерних тегов, являются ещё и все вложенные в эти дочерние теги узлы (а также узлы, вложенные в эти вложенные узлы, и т.д. ... согласно рекурсивной природе деревьев).

Дети и Потомки в DOM-дереве

Дочерние узлы одновременно являются потомками. Обратное утверждение неверно: потомок необязательно является дочерним элементом (в примере тег <span> приходится потомком, но не ребёнком по отношению к тегу <div> с id parent-div).

Элементы

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

Отношения между элементами в DOM-дереве

Все эти методы возвращают объекты типа Element и пропускают объекты Text или Comment. Это видно в примере ниже, где свойство children возвращает только теги. Этим children отличается от childNodes, который возвращает все узлы, включая листовые.

const node = document.documentElement;
node.children; // [head, body]

Между children и childNodes есть еще одно довольно важное отличие. Они возвращают не только разный набор узлов, но и сам тип коллекции в первом и втором случае разный. childNodes возвращает NodeList, а children – HTMLCollection. Они немного по-разному работают, но рассматривать эту разницу будет иметь смысл позже, когда мы познакомимся с селекторами.

Специальная навигация

Некоторые элементы обладают специальными свойствами для навигации по ним, к таким элементам относятся, например, формы и таблицы.

<table>
  <tr>
    <td>1.1</td>
    <td>1.2</td>
    <td>1.3</td>
  </tr>
  <tr>
    <td>2.1</td>
    <td>2.2</td>
    <td>2.3</td>
  </tr>
</table>
const table = document.body.firstElementChild
table.rows[0].cells[2];

Этот способ навигации не заменяет основные. Он сделан исключительно для удобства в тех местах, где это имеет смысл.

Заключение

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

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

Самостоятельная работа

  1. Откройте консоль в своем браузере.
  2. Начиная от document.body доберитесь до самых глубоких узлов, содержащих этот текст.

Дополнительные материалы

  1. Node

<span class="translation_missing" title="translation missing: ru.web.courses.lessons.mentors.mentor_avatars">Mentor Avatars</span>

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты.

Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете

Для полного доступа к курсу нужна профессиональная подписка

Профессиональная подписка откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
120
курсов
900
упражнения
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 120 курсов, 2000+ часов теории
  • 900 практических заданий в браузере
  • 360 000 студентов

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг».

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Rambler
Логотип компании Bookmate
Логотип компании Botmother

Рекомендуемые программы

С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.

Иконка программы Фронтенд-разработчик
Профессия

Фронтенд-разработчик

Разработка фронтенд-компонентов веб-приложений
16 июня 8 месяцев

Есть вопрос или хотите участвовать в обсуждении?

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг».