$ sudo teach IT
РАЗДЕЛ 9 · УРОК 2

DOM-дерево и поиск элементов

DOM — это то, как JavaScript «видит» HTML-страницу. Не текст, а живое дерево объектов. Разберём структуру DOM и научимся находить любой элемент на странице.

⏱ ~25 минут 🌳 DOM API 🎓 Для новичков

🌳 Что такое DOM и зачем он нужен

Представьте: вы написали HTML-страницу. Это просто текстовый файл — набор символов, тегов, атрибутов. Браузер читает этот файл и создаёт из него нечто гораздо более мощное — DOM (Document Object Model).

DOM — это живое дерево объектов в памяти браузера. Каждый HTML-тег становится объектом (узлом). Между ними есть отношения: родитель, дочерний элемент, сосед. JavaScript работает именно с DOM — не с текстом HTML, а с этим деревом объектов.

💡 Аналогия: HTML — это чертёж здания на бумаге. DOM — это само здание, уже построенное. JavaScript — это рабочие, которые могут ходить по зданию, переставлять мебель, открывать двери и строить новые комнаты.

Вот простая HTML-страница:

<!DOCTYPE html>
<html>
  <head>
    <title>Моя страница</title>
  </head>
  <body>
    <h1 id="title">Привет!</h1>
    <p class="text">Первый абзац</p>
    <p class="text">Второй абзац</p>
    <ul>
      <li>Пункт 1</li>
      <li>Пункт 2</li>
    </ul>
  </body>
</html>

Браузер превращает это в дерево. Схематично оно выглядит так:

document
└── html
├── head
│ └── title → "Моя страница"
└── body
├── h1#title → "Привет!"
├── p.text → "Первый абзац"
├── p.text → "Второй абзац"
└── ul
├── li → "Пункт 1"
└── li → "Пункт 2"

Каждый прямоугольник в этом дереве — узел (node). В DOM есть разные типы узлов: элементы (теги), текстовые узлы (содержимое), комментарии и другие. В 99% случаев вы работаете с элементами.

Точка входа в DOM — объект document. Это самый верхний узел, доступный из JavaScript. Через него можно добраться до любого элемента страницы.

🔍 getElementById — поиск по уникальному ID

Самый простой способ найти элемент — по его атрибуту id. ID должен быть уникальным на странице — только один элемент может иметь конкретный ID.

<!-- HTML: элемент с ID -->
<h1 id="main-title">Заголовок страницы</h1>
<button id="submit-btn">Отправить</button>
// JavaScript: нашли элемент по ID
const title = document.getElementById('main-title');
const btn = document.getElementById('submit-btn');

console.log(title); // HTMLHeadingElement (объект элемента h1)
console.log(title.textContent); // "Заголовок страницы"

// Если элемент не найден — возвращается null
const missing = document.getElementById('nope');
console.log(missing); // null

⚠️ Обратите внимание: в getElementById знак # писать не нужно. Просто имя ID: getElementById('main-title'), а не getElementById('#main-title'). Это частая ошибка новичков.

🎯 querySelector — поиск по CSS-селектору

querySelector — это мощный современный метод, который принимает любой CSS-селектор. Если вы умеете писать CSS — вы уже умеете пользоваться querySelector.

Важно: querySelector возвращает первый найденный элемент, или null если ничего не нашлось.

<!-- Вот наша HTML-страница -->
<div class="card">
  <h2 class="card-title">Карточка 1</h2>
  <p>Текст карточки</p>
  <a href="https://example.com" data-type="external">Ссылка</a>
</div>
<div class="card">
  <h2 class="card-title">Карточка 2</h2>
</div>
// querySelector принимает CSS-селекторы — те же правила, что в CSS!

// По тегу
const paragraph = document.querySelector('p'); // первый 

// По классу (нужна точка) const card = document.querySelector('.card'); // первый div.card // По ID (нужна решётка) const title = document.querySelector('#main-title'); // По атрибуту const externalLink = document.querySelector('[data-type="external"]'); // Сложные CSS-селекторы — тоже работают! const cardTitle = document.querySelector('.card .card-title'); // h2 внутри .card const firstLi = document.querySelector('ul li:first-child'); console.log(card); // HTMLDivElement — первая карточка console.log(cardTitle); // HTMLHeadingElement — заголовок первой карточки

💡 querySelector vs getElementById: querySelector('#id') и getElementById('id') делают одно и то же, но getElementById чуть быстрее. Однако querySelector удобнее — один метод для любых селекторов. В современном коде обычно используют querySelector.

📋 querySelectorAll — найти все подходящие элементы

querySelectorAll — как querySelector, но возвращает все совпадающие элементы, а не только первый. Результат — NodeList (коллекция узлов).

<ul id="todo-list">
  <li class="task">Купить хлеб</li>
  <li class="task done">Позвонить маме</li>
  <li class="task">Сходить в спортзал</li>
</ul>
// Найти все элементы с классом .task
const tasks = document.querySelectorAll('.task');
console.log(tasks); // NodeList(3) [li, li, li]
console.log(tasks.length); // 3

// NodeList можно перебрать с помощью forEach:
tasks.forEach(function(task) {
  console.log(task.textContent);
});
// "Купить хлеб"
// "Позвонить маме"
// "Сходить в спортзал"

// Или через цикл for...of:
for (const task of tasks) {
  console.log(task.textContent);
}

// Найти только выполненные задачи
const doneTasks = document.querySelectorAll('.task.done');
console.log(doneTasks.length); // 1

⚠️ NodeList — не массив! У NodeList есть forEach и length, но нет map, filter, reduce. Чтобы получить настоящий массив: Array.from(tasks) или [...tasks]. После этого доступны все методы массива.

// Превращаем NodeList в массив — получаем все методы
const tasksArray = Array.from(document.querySelectorAll('.task'));

// Теперь можем использовать filter, map и другие методы массива
const notDone = tasksArray.filter(task => !task.classList.contains('done'));
console.log(notDone.length); // 2

🗿 Устаревшие методы — встречаются в чужом коде

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

getElementsByClassName(className)
// Ищет все элементы с указанным классом
const cards = document.getElementsByClassName('card');
// Возвращает HTMLCollection — "живую" коллекцию
// Автоматически обновляется при изменении DOM!
getElementsByTagName(tagName)
// Ищет все элементы с указанным тегом
const paragraphs = document.getElementsByTagName('p');
const allElements = document.getElementsByTagName('*'); // все элементы
Старые методы (getElementsBy*)
  • Возвращают HTMLCollection
  • «Живая» — меняется вместе с DOM
  • Нет метода forEach
  • Не поддерживают сложные селекторы
Современные методы (querySelectorAll)
  • Возвращают NodeList
  • «Статичная» — снимок на момент запроса
  • Есть forEach
  • Поддерживают любые CSS-селекторы

🔬 Поиск внутри конкретного элемента

querySelector и querySelectorAll можно вызывать не только на document, но и на любом элементе. Тогда поиск ограничится потомками этого элемента.

<div class="card" id="card1">
  <h2>Заголовок 1</h2>
  <p>Текст 1</p>
</div>
<div class="card" id="card2">
  <h2>Заголовок 2</h2>
  <p>Текст 2</p>
</div>
const card1 = document.getElementById('card1');

// Ищем h2 ТОЛЬКО внутри card1, а не по всей странице
const titleInCard1 = card1.querySelector('h2');
console.log(titleInCard1.textContent); // "Заголовок 1" (не "Заголовок 2"!)

// Все параграфы внутри card1
const paragraphsInCard1 = card1.querySelectorAll('p');
console.log(paragraphsInCard1.length); // 1 (не 2!)

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

🧭 Навигация по дереву DOM

Когда вы нашли элемент, можно перемещаться по дереву от него к соседним узлам:

const item = document.querySelector('.task');

// Родительский элемент
item.parentElement; // ul#todo-list

// Дочерние элементы (только элементы, без текстовых узлов)
item.parentElement.children; // HTMLCollection всех li

// Первый и последний дочерний элемент
item.parentElement.firstElementChild; // первый li
item.parentElement.lastElementChild;  // последний li

// Следующий и предыдущий соседний элемент
item.nextElementSibling;     // следующий li
item.previousElementSibling; // предыдущий li (null если первый)

✅ Совет: используйте parentElement, children, nextElementSibling — они работают только с элементами (тегами). Есть аналоги без Element в названии (parentNode, childNodes) — они включают текстовые узлы и комментарии, что обычно ни к чему.

✅ Итоги урока

  • DOM — живое дерево объектов в памяти браузера, которое создаётся из HTML. JavaScript работает с DOM, а не с текстом HTML
  • document — точка входа. Через него доступен весь DOM
  • getElementById('id') — быстрый поиск по уникальному ID, без знака #
  • querySelector('.class') — поиск первого элемента по CSS-селектору
  • querySelectorAll('.class') — поиск всех элементов, возвращает NodeList
  • NodeList поддерживает forEach, но это не массив. Для полного набора методов: Array.from()
  • Поиск можно вызывать на элементе, а не только на document — тогда поиск идёт только в потомках
  • Навигация по дереву: parentElement, children, nextElementSibling

В следующем уроке мы научимся изменять найденные элементы — менять их текст, HTML и атрибуты. 🖊️

DOM-дерево и поиск элементов

8 вопросов

Поиск элементов по селекторам

// Дан HTML-код страницы. Напишите три вызова querySelector, // которые найдут: // 1. Элемент с id="header" // 2. Первый элемент с классом .menu-item // 3. Все элементы
  • внутри элемента с классом .nav // // Запишите результаты в переменные: // headerEl, firstMenuItem, allNavItems let headerEl = null; let firstMenuItem = null; let allNavItems = null; // Ваш код здесь
  • Навигация по DOM-дереву

    // Дан элемент item (ссылка на
  • внутри списка). // Напишите код для получения: // 1. Родительского элемента (списка
      ) — в переменную parent // 2. Следующего соседнего элемента — в переменную next // 3. Первого дочернего элемента родителя — в переменную firstChild let parent = null; let next = null; let firstChild = null; // Ваш код здесь (используйте item)