awesome-everything EN
↑ Обратно к восхождению

Браузер и фронтенд-рантайм

Layout thrash: форсированная синхронная компоновка

Суть Как чтение геометрии после записи стилей вызывает немедленный сброс компоновки, как выглядят N форсированных компоновок за кадр в DevTools и двухпроходное батч-лечение.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min

Resize-обработчик итерирует 5000 строк. Каждая строка читает свою ширину, затем записывает новую. Страница блокируется на 5 секунд. CPU не делает ничего экзотического — он запускает один и тот же расчёт компоновки 5000 раз, по разу на строку, потому что никто не сказал ему батчить.

Как работает форсированная синхронная компоновка

Компоновка управляется dirty-флагами: браузер батчит записи стилей и сбрасывает их только когда нужна свежая геометрия. Патологический случай — цикл чтение-запись в JS:

for (const row of rows) {
  const w = row.offsetWidth;        // чтение → форсирует сброс ожидающих записей
  row.style.width = w + 10 + 'px'; // запись → снова помечает компоновку dirty
}

Каждая итерация заставляет браузер вычислять компоновку для ответа на offsetWidth, затем немедленно помечает компоновку dirty для следующей итерации. N строк = N полных компоновок.

Список из 5000 строк при 1 мс на компоновку = 5 секунд блокировки главного потока. DevTools показывает это как фиолетовую Layout-полосу с предупреждением в консоли «Forced reflow while executing JavaScript took XX ms».

Жизненный цикл dirty-флага в цикле чтение-запись

Итерация 1: запись style.width (компоновка dirty)
Итерация 2: чтение offsetWidth → СБРОС компоновки (1 полная компоновка)
Итерация 2: запись style.width (компоновка снова dirty)
Итерация 3: чтение offsetWidth → СБРОС компоновки (2-я полная компоновка)
… × N итераций = N компоновок

Двухпроходное батч-лечение

Разделите все чтения и все записи:

// Двухпроходный батч: сначала читаем, потом пишем.
const widths = [];
for (const row of rows) {
  widths.push(row.offsetWidth);          // проход 1: все чтения
}
for (let i = 0; i < rows.length; i++) {
  rows[i].style.width = (widths[i] + 10) + 'px'; // проход 2: все записи
}

Поскольку ни одно чтение не следует за записью в одном цикле, браузер батчит все записи и выполняет один проход компоновки для всего батча. Временная сложность падает с O(N × компоновка) до O(компоновка).

Общее правило: в любой функции, трогающей геометрию, сначала все чтения, потом все записи.

Если разделить чтения от записей невозможно, кэшируйте ширину при монтировании компонента и пересчитывайте только при реальном изменении размера родителя.

Свойства, вызывающие форсированную компоновку

Любое свойство, возвращающее вычисленную геометрию, форсирует сброс компоновки при чтении. Полный список включает:

offsetWidth, offsetHeight, offsetTop, offsetLeft, offsetParent, clientWidth, clientHeight, clientTop, clientLeft, scrollWidth, scrollHeight, scrollTop, scrollLeft, getBoundingClientRect(), getComputedStyle(), scrollIntoView(), focus() (иногда).

Найди ошибку
log
[Violation] Forced reflow while executing JavaScript took 42 ms
  at applyRowWidths (list.js:88)
  at handleResize (list.js:34)
  at window.onresize (list.js:12)

[Violation] Forced reflow while executing JavaScript took 47 ms
  at applyRowWidths (list.js:88)
  at handleResize (list.js:34)
  at window.onresize (list.js:12)

[Violation] Forced reflow while executing JavaScript took 51 ms
  at applyRowWidths (list.js:88)
  ...

Три одинаковых предупреждения 'Forced reflow', все указывают на list.js:88 внутри resize-обработчика. Какой паттерн вызвал это и каково хирургическое лечение?

Устранение форсированной синхронной компоновки в resize-обработчике

1/3
Викторина

Scroll-обработчик читает `element.offsetTop`, затем записывает новый стиль. Браузер зависает. Что произошло?

Викторина

Вы оборачиваете цикл чтение-запись в `requestAnimationFrame`. Это лечит layout thrash?

Вспомните перед уходом
  1. 01
    Что такое форсированная синхронная компоновка (layout thrash)?
  2. 02
    Почему оборачивание цикла чтение-запись в rAF не лечит layout thrash?
  3. 03
    Назовите пять свойств, чтение которых форсирует сброс компоновки.
Итог

Форсированная синхронная компоновка (layout thrash) возникает, когда JS читает вычисленно-геометрическое свойство после записи стиля. Браузер обязан сбросить все ожидающие записи и выполнить полный проход компоновки для ответа на чтение. В цикле из N элементов это N полных компоновок — список из 5000 строк при 1 мс/компоновка блокирует главный поток на 5 секунд. Лечение — двухпроходный батч: собрать все чтения в массив в первом проходе, затем применить все записи во втором. Браузер сбрасывает компоновку один раз для всего батча. Оборачивание цикла в rAF не лечит проблему — rAF перемещает границу вызова, но не инвалидации.

Связанные уроки
встречается в143
Продолжить восхождение ↑BeginMainFrame, анимации на потоке compositor и память GPU
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.