Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Навигация по 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));
    

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

    const list = Array.from(nodes);
    // Теперь у нас обычный массив и доступные методы, например, filter
    // Можем отфильтровать нужные элементы
    const filtered = list.filter((item) => item.textContent.includes('Навигация по DOM-дереву'));
    // И извлечь из них данные, например, имена тегов
    const filteredTags = filtered.map((item) => item.tagName);
    console.log(filteredTags); // => ['HEAD', 'BODY']
    

Иерархия

Узлы 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]; // <td>1.3</td>

В примере выше table имеет специальные свойства для навигации rows и cells.

Этот способ навигации не заменяет основные способы. Он сделан исключительно для удобства в тех местах, где это имеет смысл. Например, таблица имеет ячейки в строках. Поэтому гораздо удобнее обращаться к строкам(англ. rows) и ячейкам(англ. cells) через индексы.

Заключение

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

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


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

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

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

  1. Node

Аватары экспертов Хекслета

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

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

Для полного доступа к курсу нужен базовый план

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

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

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

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

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

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

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 25 000 ₸ в месяц
Разработка фронтенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 21 ноября
профессия
от 39 525 ₸ в месяц
Разработка фронтенд- и бэкенд-компонентов для веб-приложений
16 месяцев
с нуля
Старт 21 ноября
профессия
новый
Автоматизированное тестирование веб-приложений на JavaScript
8 месяцев
c опытом
Старт 21 ноября

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

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

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»