Головна / Статті

Вузли DOM — це не рядок HTML, який ви надіслали.

DOM — це не рядок HTML, який ви надсилаєте. Дерева парсингу, динамічні списки вузлів та процеси перерозподілу пояснюють, чому мислення на рівні рядків призводить до проблем у фронтендах JavaScript та React.

Видавець: REACTAPP.TOP5 хв читання1180 слів

Джерела: The DOM Is Not Your HTML: The Difference Every Frontend Developer Should Understand

Ви створюєте HTML, проте JavaScript ніколи не змінює цей файл на диску. Він взаємодіє з поточною моделлю об’єктів документа браузера.

Коли маркап потрапляє до браузера, він формує початкову структуру сторінки. Документ може починатися з простого заголовка:

<h1>Hello</h1>

Після завантаження JavaScript може знайти цей заголовок та замінити його текст:

document.querySelector("h1").textContent = "Hello World";

Потім на екрані відображається:

<h1>Hello World</h1>

Це може здаватися так, ніби сам файл HTML був переписаний. Це не так. Файл на сервері все ще містить початковий заголовок. JavaScript змінив поточну модель сторінки в браузері — DOM. Розділення вихідного маркапу від цієї поточної моделі пояснює багато аспектів поведінки JavaScript.

HTML — це документ. DOM — це поточне представлення.

HTML — це маркап, який ви створюєте, наприклад:

<body>
  <h1>Hello</h1>
  <button>Click me</button>
</body>

Браузер парсить цей документ у структуру в пам’яті. Ця структура — це модель об’єктів документа. Опис процесу передачі виглядає так:

HTML
 │
 │ browser parses
 ▼
DOM
 │
 ├── body
 │    ├── h1
 │    └── button

DOM — це не просто копія вашого HTML у вигляді рядка. Це структурований, програмований документ. Саме цим керує JavaScript.

То що насправді змінює JavaScript?

Почніть з:

<h1 id="title">Hello</h1>

Потім виконайте:

const title = document.querySelector("#title");
title.textContent = "Welcome";

Скрипт знаходить вузол DOM та оновлює його текст. Тепер живе дерево відображає:

<h1 id="title">Welcome</h1>

Оригінальний HTML-код не був переписаний. Якщо маркування походить з сервера чи статичного файлу, цей код все ще містить:

<h1 id="title">Hello</h1>

Різниця незначна, але критична: зміна DOM — це не те саме, що редагування HTML-коду.

Ось простий спосіб побачити різницю

Відкрийте сторінку та змініть щось через консоль:

document.querySelector("h1").textContent = "Changed!";

Видима сторінка оновлюється миттєво. Порівняйте живий DOM:

<h1>Changed!</h1>

з оригінальним HTML-джерелом:

<h1>Hello</h1>

Вони можуть відрізнятися, тому що браузер працює зі своїм живим DOM.

DOM — це більше, ніж те, що ви спочатку написали

Припустимо, що маркування починається так:

<ul id="users">
  <li>John</li>
</ul>

JavaScript може додати ще один елемент:

const list = document.querySelector("#users");
const item = document.createElement("li");
item.textContent = "Sarah";
list.appendChild(item);

Тепер жива структура містить:

ul
├── li "John"
└── li "Sarah"

„Сара“ ніколи не з’являлась у оригінальному файлі. Браузер не вимагає переписування джерельного документа; скрипт змінив живий DOM. Саме ця можливість є основою інтерактивних сайтів.

DOM — це дерево

„Модель об’єктів“ звучить складніше, ніж є насправді. У найпростішому вигляді DOM — це дерево вузлів. Якщо маємо:

<main>
  <h1>Hello</h1>
  <p>Welcome!</p>
</main>

браузер створює структуру приблизно таку:

Document
   │
   └── main
       ├── h1
       │    └── "Hello"
       │
       └── p
            └── "Welcome!"

Саме завдяки такій структурі дерева скрипти можуть працювати з взаємозв’язками на кшталт:

element.parentElement
element.children
element.nextElementSibling

Ви не скануєте звичайний текстовий файл; ви навігуєте по структурованому дереву.

І DOM може змінюватися

Вихідний HTML зазвичай є початковим документом. Дерево в пам’яті залишається змінним під час виконання. Скрипти можуть виділяти вузли:

const button = document.createElement("button");

встановлювати атрибути:

button.setAttribute("disabled", "");

змінювати вміст:

button.textContent = "Loading...";

та додавати вузли:

document.body.append(button);

Дерево еволюціонує під час роботи додатку, тому інтерактивні інтерфейси не потребують повного завантаження HTML після кожної незначної зміни.

Але є ще одна річ, яку розробники часто плутають

Перевірка сторінки може виявити HTML, якого ніколи не було у початковому вихідному коді. Скрипт може створити:

<div class="notification">
  Saved successfully
</div>

Ви не створювали цей вузол, проте DevTools можуть показати його у панелі Elements, оскільки ця панель відображає DOM. Опція View Source може повністю пропустити цей вузол.

Тому результати використання View Source та Inspect Element не завжди збігатимуться. Однією з поширених причин є те, що JavaScript змінює DOM після початкового завантаження HTML.

Чому це важливо для розробників фронтенду?

Багато аспектів функціонування фронтенду стають зрозумілішими, коли ментальна модель змінюється з „JavaScript змінює HTML“ на „JavaScript змінює DOM“. Саме таке формулювання пояснює роботу таких API:

element.classList.add("active");
element.remove();
element.textContent = "Loading...";
element.append(newElement);

Ці виклики працюють із актуальною структурою документа. Браузер потім використовує цю оновлену структуру під час відображення екрана.

А як щодо React?

Та сама різниця допомагає під час вивчення React. Ви можете написати:

return <h1>Hello</h1>;

JSX — це не сам DOM. Це синтаксис, який описує користувацький інтерфейс. React вирішує, як цей опис має відображатися у реальному DOM. Розуміння DOM перед тим, як приступити до абстракцій фреймворку, робить процеси відображення, узгодження даних, роботу з посиланнями та оновлення DOM менш загадковими.

Вам не потрібні всі внутрішні деталі. Пам’ятайте про цю послідовність дій:

HTML
  ↓
Browser parses it
  ↓
DOM
  ↓
JavaScript can modify it
  ↓
Browser updates the rendered page

Фреймворки додають додаткові шари, але основою залишається DOM браузера.

Один поширений помилковий підхід

Розробники іноді сприймають зміни в DOM як постійну правку оригінального HTML. Це не так. Наприклад:

document.body.innerHTML = "<h1>New page</h1>";

Це завдання може замінити поточну динамічну структуру, проте воно не переписує файл HTML на сервері. Оновлення завантажує документ з його вихідного джерела. Якщо зміни не були збережені та не створено нового HTML, повертається початковий документ. Саме ця різниця відокремлює стан під час виконання від початкового контенту.

Модель мислення, яку потрібно пам’ятати

Уникайте зведення ідей до одного:

HTML = DOM

Віддавайте перевагу підходу, ближчому до:

HTML
 │
 │ parsed by browser
 ▼
DOM
 │
 │ modified by JavaScript
 ▼
Updated DOM
 │
 │ used during rendering
 ▼
What the user sees

Розглядайте HTML як оригінальний документ-джерело. Розглядайте DOM як змінюваний дерево браузера, створене на основі цього джерела. Скрипти змінюють це дерево; відображення відповідає тому, що наразі знаходиться в дереві.

Остаточний висновок

Не ототожнюйте DOM із файлом HTML. Маркап — це те, що ви створюєте; об’єктна модель — це те, що браузер підтримує активним, поки вкладка відкрита. Завдяки цьому розділенню стає зрозумілим кілька аспектів: чому скрипти можуть додавати та видаляти вузли, чому працює querySelector(), чому textContent оновлює те, що ви бачите, чому інструменти розробника можуть показувати вузли, які ви ніколи не створювали, чому «живе дерево» може відрізнятися від вихідного коду та чому React все одно в кінцевому підсумку впливає на DOM.

Корисний підхід простий: ви пишете HTML, браузер створює DOM, а JavaScript змінює DOM. Як тільки це стає зрозумілим, DOM перестає здаватися незрозумілим API та починає виглядати таким, яким він є — живим представленням сторінки у браузері.