От URL до первого байта: DNS, TCP/TLS, HTTP, кеши и Service Worker
Подготовка к собеседованию в Яндекс: Browser Pipeline, Promises и Генераторы
На технических интервью в крупные компании (как Яндекс, Avito, Тинькофф) требуют глубокого понимания того, как работает платформа. Вас спросят не только о том, как написать код, но и о том, какой ценой он выполняется. В этой статье мы разберем полный путь работы браузера, механизмы рендеринга и внутреннее устройство асинхронности в JavaScript.
Часть 1. Browser Pipeline: От URL до пикселей
Этот процесс делится на два больших этапа: сетевой (доставка ресурсов) и рендеринг (превращение кода в картинку).
1. Сетевой этап (Network)
Когда пользователь вводит URL и нажимает Enter, браузер выполняет следующие шаги:
- Разбор URL и проверка кешей. Браузер проверяет HSTS (нужен ли HTTPS), Service Worker и HTTP-кеш (Disk/Memory). Если ресурс свежий, сеть не используется.
- DNS (Domain Name System). Если кеша нет, браузер должен узнать IP-адрес сервера. Запрос идет от локального кеша к DNS-серверу провайдера и далее рекурсивно.
- Установка соединения (TCP/TLS).
- TCP Handshake: Обмен пакетами
SYN→SYN-ACK→ACKдля создания надежного канала. - TLS Handshake: Обмен сертификатами и ключами шифрования. В TLS 1.3 это происходит быстрее (1 RTT), но это все равно затратная операция.
- TCP Handshake: Обмен пакетами
- HTTP-запрос и ответ. Отправляется запрос (GET). Сервер думает (Time To First Byte — TTFB) и начинает отдавать HTML.
2. Построение объектных моделей (Parsing)
Как только браузер получает первые байты HTML, он начинает парсинг, не дожидаясь загрузки всего документа.
- HTML → DOM (Document Object Model).
- Байты → Символы → Токены (теги) → Узлы (Nodes) → DOM-дерево.
- Если парсер встречает
<script>, он блокирует построение DOM (если нет атрибутовasyncилиdefer), загружает и выполняет JS.
- CSS → CSSOM (CSS Object Model).
- Браузер загружает стили и строит дерево правил. CSSOM блокирует рендеринг: браузер не покажет контент, пока не поймет, как его стилизовать.
3. Render Tree
Браузер объединяет DOM и CSSOM в Render Tree (дерево рендеринга). В него попадают только видимые элементы.
- Элементы с
display: noneне попадают в Render Tree. - Элементы с
visibility: hiddenпопадают (они занимают место, просто прозрачные). - Псевдоэлементы (
::before,::after) добавляются сюда.
4. Layout (Reflow) — Компоновка
На этом этапе браузер рассчитывает геометрию каждого элемента: точные координаты () и размеры () в пикселях экрана. Процесс идет рекурсивно от <html> вглубь.
Понятие Reflow (перекомпоновка) означает повторный запуск этого процесса при изменениях.
5. Paint (Repaint) — Отрисовка
Браузер заполняет пиксели: цвета, фоны, тени, границы, текст. Отрисовка часто происходит на нескольких слоях.
Понятие Repaint (перерисовка) означает обновление внешнего вида без изменения геометрии.
6. Composite — Композиция
Финальный этап. Браузер собирает все слои в итоговое изображение на экране. Этот этап выполняется на GPU (видеокарте), что делает его очень быстрым.
Часть 2. Reflow, Repaint и Composite: Детальный разбор
Понимание этих терминов критично для оптимизации производительности (60 FPS).
Reflow (Layout)
Самая дорогая операция. Происходит, когда меняется геометрия страницы или структура дерева.
Что вызывает Reflow:
- Добавление/удаление DOM-узлов.
- Изменение размеров окна браузера (resize).
- Изменение шрифта.
- Изменение свойств:
width,height,padding,margin,border,left,top. - Чтение геометрических свойств (Forced Synchronous Layout). Если вы меняете стиль, а потом сразу читаете
offsetWidth, браузер обязан прервать JS и срочно пересчитать макет, чтобы дать актуальные данные.
Repaint
Средняя по стоимости операция. Происходит, когда меняется внешний вид, но не геометрия.
Что вызывает Repaint:
color,background-color.visibility.box-shadow,outline.
Composite
Самая дешевая операция. Если свойство можно обработать только на этапе композиции, Layout и Paint пропускаются.
Свойства для Composite (GPU):
transform(перемещение, масштабирование, вращение).opacity(прозрачность).filter(некоторые фильтры).
Для плавных анимаций всегда используйте
transformиopacity. Изменениеleft/topвызывает Reflow каждый кадр, что нагружает CPU.
Часть 3. Promises (Промисы)
Промис — это объект, представляющий результат асинхронной операции, который будет получен в будущем. Это замена callback-hell.
Внутреннее устройство
У промиса есть три состояния:
- Pending (ожидание) — исходное состояние.
- Fulfilled (исполнено) — операция завершена успешно, есть результат (
value). - Rejected (отклонено) — произошла ошибка, есть причина (
reason).
Смена состояния необратима. Из Fulfilled нельзя перейти в Rejected и наоборот.
Microtask Queue
Обработчики .then(), .catch(), .finally() выполняются асинхронно через Microtask Queue. Микрозадачи имеют приоритет перед макрозадачами (setTimeout, I/O) и выполняются сразу после текущего синхронного кода, но до рендеринга.
Пример порядка выполнения:
console.log('1'); // Синхронно
setTimeout(() => console.log('2'), 0); // Макрозадача
Promise.resolve().then(() => console.log('3')); // Микрозадача
console.log('4'); // Синхронно
// Результат: 1, 4, 3, 2
Ключевые методы
Promise.all([p1, p2])— ждет выполнения всех. Если хоть один упал — падает весьall.Promise.allSettled([p1, p2])— ждет завершения всех (неважно, успех или ошибка). Возвращает массив статусов.Promise.race([p1, p2])— возвращает результат первого завершившегося (успех или ошибка).Promise.any([p1, p2])— возвращает первый успешный. Ошибка будет только если все упали.
Часть 4. Генераторы (Generators)
Генераторы — это функции, выполнение которых можно приостанавливать и возобновлять. Они лежат в основе async/await и библиотек вроде Redux-Saga.
Синтаксис и работа
Обозначаются звездочкой function*. При вызове код функции не выполняется сразу, а возвращается объект итератора.
function* numbers() {
yield 1;
yield 2;
return 3;
}
const gen = numbers(); // Получили итератор
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: true }
Ключевое слово yield
yield делает две вещи:
- Возвращает значение наружу (как
return). - Ставит выполнение функции на паузу, сохраняя локальные переменные.
Двусторонний обмен данными
В генератор можно передавать данные через next(value). Значение попадет в переменную слева от yield, на котором стояла пауза.
function* listener() {
const input = yield 'Waiting...';
console.log('Got:', input);
}
const gen = listener();
console.log(gen.next()); // Запуск до первого yield. Вывод: { value: 'Waiting...', done: false }
gen.next('Hello!'); // Возобновление. В input попадет 'Hello!'. Console: Got: Hello!
Это позволяет писать асинхронный код в синхронном стиле, что и реализовано в синтаксическом сахаре async/await (где await — это автоматический yield промиса).
Итоги
- Пайплайн браузера: URL → DNS → TCP/TLS → HTTP → Parsing (DOM/CSSOM) → Render Tree → Layout → Paint → Composite.
- Оптимизация рендеринга: Избегайте Reflow (изменения геометрии). Для анимаций используйте
transformиopacity, так как они вызывают только Composite и обрабатываются на GPU. - Промисы: Управляют асинхронностью через Microtask Queue. Важно знать методы
all,race,allSettled,any. - Генераторы: Функции с возможностью паузы (
yield). Позволяют создавать итераторы и управлять потоком выполнения, являясь фундаментом дляasync/await.