Узлы DOM — это не строка HTML, которую вы отправляете.
DOM — это не строка HTML, которую вы отправляете. Деревья парсинга, динамические списки узлов и перерисовка объясняют, почему подход, основанный на работе со строками, не срабатывает в фронтендах JavaScript и React.
Источники: 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. Опция «Просмотр исходного кода» может полностью пропустить этот узел.
Поэтому результаты использования функций «Просмотр исходного кода» и «Проверка элемента» не всегда совпадают. Одной из распространённых причин является изменение структуры DOM с помощью JavaScript после первоначальной загрузки 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 перестаёт казаться непрозрачным API и начинает выглядеть так, каков он есть на самом деле — как динамическое представление страницы в браузере.