Virtual DOM: что я понял после сотен проведенных собеседований
- суббота, 28 февраля 2026 г. в 00:00:10
За последние пару лет я провёл сотни технических собеседований — от junior до senior специалистов. И я обнаружил что есть одна тема, с пониманием которой есть проблемы почти у всех кандидатов с кем мне доводилось общаться. Это Virtual DOM.
Почти каждый кандидат объясняет его примерно так:
“Virtual DOM нужен, чтобы обновлять не всю страницу, а только её часть. Поэтому всё работает быстрее.”
Формулировка вроде звучит логично. Но проблема в том, что это неверное понимание в принципе.
Браузер и без всякого Virtual DOM не перерисовывает «всю страницу» при каждом изменении. Он обновляет только затронутые участки. Да и в целом невозможно повлиять на flow работы браузерного движка взаимодействуя с DOM через Virtual DOM.
Значит, дело не в этом.
Чтобы понять Virtual DOM по-настоящему, нужно выйти из плоскости “как сделать быстрее” и перейти в плоскость “как управлять системой”.
В браузере есть два разных мира:
JavaScript-движок
Rendering Engine (DOM, layout, paint и т.д.)
DOM — это не "обычный объект" в JavaScript. Это интерфейс к внутренней системе браузера.
Когда вы пишете:
element.textContent = 'Hello'
Вы:
пересекаете границу JS → браузер,
модифицируете DOM-дерево,
потенциально запускаете пересчёт стилей,
возможно инициируете relayout,
возможно запускаете repaint.
Да, современные браузеры хорошо оптимизированы и они не перерисовывают всю страницу целиком при любом мельчайшем изменении, но взаимодействие с DOM — это host-операции и они всегда дороже, нежели чем операции внутри JS. Дороже - значит медленнее.
Теперь представьте приложение без Virtual DOM.
У вас есть:
состояние пользователя
список задач
фильтры
загрузка
ошибки
асинхронные запросы
Всё это нужно синхронизировать с интерфейсом.
В качестве примера возьмем простенькую реализацию с манипуляциями состояния кнопки:
function updateButton() { if (isDisabled) { button.classList.add('disabled') button.setAttribute('disabled', '') } else { button.classList.remove('disabled') button.removeAttribute('disabled') } if (isLoading) { button.classList.add('loading') spinner.style.display = 'block' } else { button.classList.remove('loading') spinner.style.display = 'none' } if (hasError) { button.classList.add('error') } else { button.classList.remove('error') } }
Пока состояний мало — всё управляемо.
Но теперь представьте:
isLoading приходит из одного запроса,
isDisabled из другого,
hasError меняется асинхронно,
а isActive обновляется через WebSocket.
DOM становится:
местом, где хранится промежуточное состояние,
объектом, с которым нужно синхронизироваться вручную,
источником потенциальной рассинхронизации.
Вот где начинается настоящая проблема.
Без абстракции получается двусторонняя связь:
State ↔ DOM
Вы:
меняете состояние,
обновляете DOM,
иногда читаете DOM,
принимаете решения на основе DOM.
Например:
if (button.classList.contains('active')) { // что-то делаем }
DOM начинает участвовать в логике. А он для этого не предназначен.
Virtual DOM — это попытка перенести управление интерфейсом полностью в JavaScript. Вместо того чтобы работать с DOM напрямую, мы создаём его модель:
{ type: 'button', props: { class: isActive ? 'active' : '', disabled: isDisabled }, children: isLoading ? 'Loading...' : 'Submit' }
Это просто объект.
Он:
не вызывает перерасчетов layout,
не запускает repaint,
не имеет побочных эффектов.
Это просто данные.
С Virtual DOM архитектура становится односторонней:
State → Virtual DOM → Real DOM
Реальный DOM перестаёт быть активным участником логики. Он становится проекцией состояния. И это фундаментальное изменение.
В маленьком проекте можно жить без абстракции, в большом — нет.
Когда приложение растёт:
состояний становится больше,
асинхронности больше,
компонентов больше,
зависимостей больше.
Без единой модели вы получаете хаос синхронизации. В то время как Virtual DOM позволяет четко разграничить состояние от визуализации:
DOM — это UI, отображающий текущее состояние.
Иными словами Virtual DOM дает нам архитектурные гарантии.
Когда состояние меняется:
Пересобирается виртуальное дерево.
Оно сравнивается с предыдущим.
Вычисляется разница.
В DOM применяются только необходимые изменения.
Вся сложность:
вычисляется в JS,
сравнивается в JS,
планируется в JS.
То есть в браузер отправляется минимальный набор мутаций. Таким образом мы уменьшаем количество переходов из JS → DOM и получаем полный контроль над дорогими операциями и это самый важный момент. Так как в конечном итоге мы все равно заплатим цену за финальные изменения в UI, но ключевое тут "за финальные изменения".
Да, содержание и поддержание в актуальном состоянии Virtual DOM добавляет накладных расходов на:
создание объектов,
хранение предыдущего дерева,
diff-алгоритм,
patch.
Это дополнительная CPU-нагрузка. Но эта нагрузка происходит внутри JavaScript — в контролируемой и предсказуемой среде. Мы платим вычислениями за архитектурную стабильность.
Virtual DOM — это не про “обновлять только часть страницы”.
Это про:
перенос контроля в JavaScript,
устранение ручной синхронизации,
минимизацию дорогостоящих host-операций,
превращение DOM в конечное устройство вывода.
Virtual DOM не делает браузер быстрее. Он делает систему управляемой.
Если формулировать точно:
Virtual DOM — это способ перестать управлять DOM напрямую и начать управлять данными, из которых DOM вычисляется. Это архитектурный слой между вашим кодом и браузером. И только после понимания этого имеет смысл говорить про diff, patch и оптимизации конкретных фреймворков.
Если вам интересна тема, то в следующей статье можно разобрать то, как именно Virtual DOM реализован во Vue 3 и насколько он уже оптимизирован на уровне runtime.