Как строится DOM дерево при загрузке HTML документа
Что проверяют
Понимает ли кандидат критический путь рендеринга: что парсер работает потоково, почему скрипты и стили влияют на скорость первой отрисовки и чем async отличается от defer.
Как отвечать
Сначала цепочка: байты ответа, декодирование по указанной кодировке, токенизация, создание узлов, сборка дерева DOM. Парсер потоковый, дерево строится по мере прихода данных, ждать конца документа не нужно.
Параллельно из стилей собирается CSSOM, отдельное дерево. CSS в DOM не попадает. Дальше DOM и CSSOM объединяются в render tree (узлы с display: none в него не входят, visibility: hidden входит), затем layout, paint и композиция слоёв.
Скрипты и парсинг:
- обычный
<script src>останавливает парсер: и загрузка, и выполнение блокируют построение DOM; deferкачается параллельно, выполняется после построения DOM, в порядке объявления, доDOMContentLoaded;asyncвыполняется сразу после загрузки, может прервать парсинг, порядок выполнения не гарантирован;type="module"по умолчанию ведёт себя какdefer.
Пока парсер стоит на скрипте, работает preload scanner: он забегает вперёд по разметке и заранее скачивает стили, скрипты и картинки. Именно поэтому блокирующий скрипт тормозит построение дерева, но не убивает загрузку ресурсов целиком.
Нюанс, который отличает сильный ответ: CSS не блокирует парсинг HTML, но блокирует отрисовку, и не загруженный стиль задерживает выполнение следующего синхронного скрипта, ведь скрипт может прочитать вычисленные стили. Так CSS блокирует парсинг косвенно.
События: DOMContentLoaded наступает после разбора разметки и выполнения defer-скриптов; картинки и async он не ждёт. load ждёт все подресурсы.
После этого дерево живёт дальше: изменения через appendChild или innerHTML вызывают перестроение, а чтение offsetHeight сразу после записи заставляет браузер синхронно пересчитать layout, что даёт layout thrashing в циклах.
Где спотыкаются
Говорят, что стили «применяются к DOM», путая CSSOM с DOM. И почти всегда сваливают async с defer в одну кучу «неблокирующих», не называя разницу в порядке выполнения и в моменте относительно DOMContentLoaded.