За последние пару лет я провёл сотни технических собеседований — от junior до senior специалистов. И я обнаружил что есть одна тема, с пониманием которой есть проблемы почти у всех кандидатов с кем мне доводилось общаться. Это Virtual DOM.

Почти каждый кандидат объясняет его примерно так:

“Virtual DOM нужен, чтобы обновлять не всю страницу, а только её часть. Поэтому всё работает быстрее.”

Формулировка вроде звучит логично. Но проблема в том, что это неверное понимание в принципе.

Браузер и без всякого Virtual DOM не перерисовывает «всю страницу» при каждом изменении. Он обновляет только затронутые участки. Да и в целом невозможно повлиять на flow работы браузерного движка взаимодействуя с DOM через Virtual DOM.

Значит, дело не в этом.

Чтобы понять Virtual DOM по-настоящему, нужно выйти из плоскости “как сделать быстрее” и перейти в плоскость “как управлять системой”.

Два мира внутри браузера

В браузере есть два разных мира:

  1. JavaScript-движок

  2. Rendering Engine (DOM, layout, paint и т.д.)

DOM — это не "обычный объект" в JavaScript. Это интерфейс к внутренней системе браузера.

Когда вы пишете:

element.textContent = 'Hello'

Вы:

  • пересекаете границу JS → браузер,

  • модифицируете DOM-дерево,

  • потенциально запускаете пересчёт стилей,

  • возможно инициируете relayout,

  • возможно запускаете repaint.

Да, современные браузеры хорошо оптимизированы и они не перерисовывают всю страницу целиком при любом мельчайшем изменении, но взаимодействие с DOM — это host-операции и они всегда дороже, нежели чем операции внутри JS. Дороже - значит медленнее.

Настоящая проблема: DOM как часть логики

Теперь представьте приложение без 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 становится:

  • местом, где хранится промежуточное состояние,

  • объектом, с которым нужно синхронизироваться вручную,

  • источником потенциальной рассинхронизации.

Вот где начинается настоящая проблема.

Когда DOM становится источником истины

Без абстракции получается двусторонняя связь:

State ↔ DOM

Вы:

  • меняете состояние,

  • обновляете DOM,

  • иногда читаете DOM,

  • принимаете решения на основе DOM.

Например:

if (button.classList.contains('active')) { 
  // что-то делаем
}

DOM начинает участвовать в логике. А он для этого не предназначен.


Virtual 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 дает нам архитектурные гарантии.


Что происходит при изменении состояния

Когда состояние меняется:

  1. Пересобирается виртуальное дерево.

  2. Оно сравнивается с предыдущим.

  3. Вычисляется разница.

  4. В DOM применяются только необходимые изменения.

Вся сложность:

  • вычисляется в JS,

  • сравнивается в JS,

  • планируется в JS.

То есть в браузер отправляется минимальный набор мутаций. Таким образом мы уменьшаем количество переходов из JS → DOM и получаем полный контроль над дорогими операциями и это самый важный момент. Так как в конечном итоге мы все равно заплатим цену за финальные изменения в UI, но ключевое тут "за финальные изменения".

Virtual DOM - это компромисс

Да, содержание и поддержание в актуальном состоянии Virtual DOM добавляет накладных расходов на:

  • создание объектов,

  • хранение предыдущего дерева,

  • diff-алгоритм,

  • patch.

Это дополнительная CPU-нагрузка. Но эта нагрузка происходит внутри JavaScript — в контролируемой и предсказуемой среде. Мы платим вычислениями за архитектурную стабильность.

Главное, что нужно понять

Virtual DOM — это не про “обновлять только часть страницы”.

Это про:

  • перенос контроля в JavaScript,

  • устранение ручной синхронизации,

  • минимизацию дорогостоящих host-операций,

  • превращение DOM в конечное устройство вывода.

Virtual DOM не делает браузер быстрее. Он делает систему управляемой.


Итог

Если формулировать точно:

Virtual DOM — это способ перестать управлять DOM напрямую и начать управлять данными, из которых DOM вычисляется. Это архитектурный слой между вашим кодом и браузером. И только после понимания этого имеет смысл говорить про diff, patch и оптимизации конкретных фреймворков.

Если вам интересна тема, то в следующей статье можно разобрать то, как именно Virtual DOM реализован во Vue 3 и насколько он уже оптимизирован на уровне runtime.