Загрузка страницы — это многоступенчатый процесс: сначала браузер устанавливает сетевое соединение (DNS + TCP), получает HTML и парсит его в DOM-дерево, параллельно строит CSSOM из стилей, затем объединяет их в Render Tree, вычисляет расположение элементов (Layout), рисует пиксели (Paint) и выводит результат на экран (Compositing). Понимание этого процесса критично для оптимизации производительности сайта.
[Браузер] → DNS lookup → IP-адрес сервера
→ TCP Handshake (SYN → SYN-ACK → ACK)
→ TLS Handshake (для HTTPS)
→ HTTP GET /index.html
← HTTP 200 OK + тело HTML**DNS lookup** — перевод домена (example.com) в IP-адрес. Результат кешируется.
**TCP соединение** — три рукопожатия перед передачей данных.
**TTFB (Time To First Byte)** — время от запроса до первого байта ответа. Важная метрика.
Браузер читает HTML сверху вниз и строит **DOM-дерево** (Document Object Model):
HTML-текст → Tokenizer → Parser → DOM Tree
<html>
<head>...</head>
<body>
<div id="app">
<p>Hello</p>
</div>
</body>
</html>
Document
│
html
/ \
head body
│
div#app
│
p
│
"Hello"Параллельно с DOM строится **CSSOM** (CSS Object Model). CSS — **блокирующий ресурс для рендеринга**: браузер не начнёт отрисовку, пока не загрузит и не распарсит все CSS-файлы в <head>.
Синхронный <script> **блокирует парсинг HTML**:
HTML парсится → встретили <script src="app.js"> →
СТОП парсинга → скачать app.js → выполнить →
ПРОДОЛЖИТЬ парсинг HTMLПочему? Скрипт может вызвать document.write() и полностью изменить HTML.
**async** и **defer** убирают блокировку:
Обычный: HTML ███░░░░░░███ (░ = пауза на скрипт)
async: HTML ████████████ (скрипт скачивается параллельно, выполняется сразу)
defer: HTML ████████████ (скрипт скачивается параллельно, выполняется после парсинга)| Атрибут | Скачивание | Выполнение | Порядок |
|---------|-----------|-----------|---------|
| (нет) | блокирует | сразу | да |
| async | параллельно | как скачается | нет |
| defer | параллельно | после парсинга HTML | да |
DOM + CSSOM объединяются в **Render Tree** — только видимые элементы с применёнными стилями. Элементы с display: none не попадают в Render Tree.
Браузер вычисляет **точные размеры и позиции** каждого элемента. Это дорогая операция — изменение геометрии любого элемента может перезапустить Layout для всей страницы.
Браузер рисует пиксели: цвета, тексты, изображения, тени. Каждый слой рисуется отдельно.
GPU объединяет слои в финальное изображение на экране.
// DOMContentLoaded — HTML распарсен, DOM готов
// Скрипты с defer выполнились. Картинки могут ещё грузиться.
document.addEventListener('DOMContentLoaded', () => {
console.log('DOM готов — можно работать с элементами')
})
// load — ВСЁ загружено: картинки, стили, скрипты
window.addEventListener('load', () => {
console.log('Страница полностью загружена')
})**Правило**: используй DOMContentLoaded для инициализации UI. Используй load только когда нужны размеры картинок.
Минимальная цепочка для первой отрисовки страницы:
HTML → DOM → Render Tree → Layout → Paint
CSS → CSSOM ↗Оптимизация CRP:
<head> (чтобы не блокировать рендеринг после парсинга)<body> или с defer<link rel="preload"> для критических ресурсов**Начни с общей картины**: "Загрузка страницы проходит в несколько этапов: сеть, парсинг, рендеринг". Это показывает системное мышление.
**Обязательно упомяни**: блокирующий характер синхронных скриптов и CSS — это ключевой момент для оптимизации. Разницу между DOMContentLoaded и load события.
**Хорошее дополнение**: упомяни Critical Rendering Path и что ты делал бы для оптимизации (defer, preload, CSS в head).
**Время ответа**: 2-3 минуты. Можно нарисовать схему на доске.
1. **"Браузер просто открывает страницу"** — полное непонимание процесса. Это показывает, что кандидат никогда не занимался оптимизацией.
2. **Путаница DOMContentLoaded и load** — очень частая ошибка, из-за которой возникают баги с "элемент не найден".
3. **Незнание про блокирующие скрипты** — если не знаешь, почему <script> в <head> без defer замедляет загрузку, это серьёзный пробел.
Симуляция последовательности загрузки браузера с временными метками
// Симуляция этапов загрузки браузера
// В реальности эти этапы выполняет движок браузера
function simulateBrowserLoading(url) {
const steps = []
const startTime = Date.now()
function log(step, detail = '') {
const elapsed = Date.now() - startTime
steps.push({ step, detail, ms: elapsed })
console.log(`[${String(elapsed).padStart(4)}ms] ${step}${detail ? ': ' + detail : ''}`)
}
console.log(`=== Загрузка ${url} ===\n`)
// 1. Сеть
log('DNS lookup', 'example.com → 93.184.216.34')
log('TCP Handshake', 'SYN → SYN-ACK → ACK')
log('TLS Handshake', 'HTTPS шифрование')
log('HTTP GET', `GET / HTTP/1.1 Host: ${url}`)
log('HTTP 200 OK', 'Получен HTML (12 KB)')
// 2. Парсинг
log('HTML Parsing', 'Построение DOM-дерева...')
log('CSS Found', 'Найден <link rel="stylesheet"> — блокирует рендеринг!')
log('CSS Parsing', 'Построение CSSOM...')
log('Script Found', 'Найден <script defer> — скачивается параллельно')
log('DOM Ready', 'DOMContentLoaded событие')
// 3. Рендеринг
log('Render Tree', 'DOM + CSSOM объединены')
log('Layout', 'Вычисление позиций и размеров элементов')
log('Paint', 'Отрисовка пикселей')
log('Compositing', 'GPU объединяет слои')
// 4. Финал
log('Script Executed', 'defer-скрипты выполнены')
log('Images Loaded', 'Все ресурсы загружены')
log('load event', 'window.onload вызван')
console.log('\n=== Итоговая последовательность ===')
return steps
}
simulateBrowserLoading('example.com')
// Демонстрация разницы async/defer/обычный
console.log('\n=== Влияние скриптов на парсинг ===')
const scenarios = [
{
type: 'Обычный <script>',
timeline: [
'Парсинг HTML....',
'⏸️ ПАУЗА: скачиваем script.js',
'⏸️ ПАУЗА: выполняем script.js',
'Продолжаем парсинг HTML',
'DOMContentLoaded'
]
},
{
type: '<script async>',
timeline: [
'Парсинг HTML.... (параллельно скачиваем script.js)',
'⏸️ ПАУЗА: выполняем script.js (как только скачался)',
'Продолжаем парсинг HTML',
'DOMContentLoaded'
]
},
{
type: '<script defer>',
timeline: [
'Парсинг HTML.... (параллельно скачиваем script.js)',
'DOMContentLoaded ← скрипт выполняется ЗДЕСЬ',
'Выполняем script.js (после полного парсинга)',
]
}
]
for (const scenario of scenarios) {
console.log(`\n${scenario.type}:`)
scenario.timeline.forEach((step, i) => {
console.log(` ${i + 1}. ${step}`)
})
}Загрузка страницы — это многоступенчатый процесс: сначала браузер устанавливает сетевое соединение (DNS + TCP), получает HTML и парсит его в DOM-дерево, параллельно строит CSSOM из стилей, затем объединяет их в Render Tree, вычисляет расположение элементов (Layout), рисует пиксели (Paint) и выводит результат на экран (Compositing). Понимание этого процесса критично для оптимизации производительности сайта.
[Браузер] → DNS lookup → IP-адрес сервера
→ TCP Handshake (SYN → SYN-ACK → ACK)
→ TLS Handshake (для HTTPS)
→ HTTP GET /index.html
← HTTP 200 OK + тело HTML**DNS lookup** — перевод домена (example.com) в IP-адрес. Результат кешируется.
**TCP соединение** — три рукопожатия перед передачей данных.
**TTFB (Time To First Byte)** — время от запроса до первого байта ответа. Важная метрика.
Браузер читает HTML сверху вниз и строит **DOM-дерево** (Document Object Model):
HTML-текст → Tokenizer → Parser → DOM Tree
<html>
<head>...</head>
<body>
<div id="app">
<p>Hello</p>
</div>
</body>
</html>
Document
│
html
/ \
head body
│
div#app
│
p
│
"Hello"Параллельно с DOM строится **CSSOM** (CSS Object Model). CSS — **блокирующий ресурс для рендеринга**: браузер не начнёт отрисовку, пока не загрузит и не распарсит все CSS-файлы в <head>.
Синхронный <script> **блокирует парсинг HTML**:
HTML парсится → встретили <script src="app.js"> →
СТОП парсинга → скачать app.js → выполнить →
ПРОДОЛЖИТЬ парсинг HTMLПочему? Скрипт может вызвать document.write() и полностью изменить HTML.
**async** и **defer** убирают блокировку:
Обычный: HTML ███░░░░░░███ (░ = пауза на скрипт)
async: HTML ████████████ (скрипт скачивается параллельно, выполняется сразу)
defer: HTML ████████████ (скрипт скачивается параллельно, выполняется после парсинга)| Атрибут | Скачивание | Выполнение | Порядок |
|---------|-----------|-----------|---------|
| (нет) | блокирует | сразу | да |
| async | параллельно | как скачается | нет |
| defer | параллельно | после парсинга HTML | да |
DOM + CSSOM объединяются в **Render Tree** — только видимые элементы с применёнными стилями. Элементы с display: none не попадают в Render Tree.
Браузер вычисляет **точные размеры и позиции** каждого элемента. Это дорогая операция — изменение геометрии любого элемента может перезапустить Layout для всей страницы.
Браузер рисует пиксели: цвета, тексты, изображения, тени. Каждый слой рисуется отдельно.
GPU объединяет слои в финальное изображение на экране.
// DOMContentLoaded — HTML распарсен, DOM готов
// Скрипты с defer выполнились. Картинки могут ещё грузиться.
document.addEventListener('DOMContentLoaded', () => {
console.log('DOM готов — можно работать с элементами')
})
// load — ВСЁ загружено: картинки, стили, скрипты
window.addEventListener('load', () => {
console.log('Страница полностью загружена')
})**Правило**: используй DOMContentLoaded для инициализации UI. Используй load только когда нужны размеры картинок.
Минимальная цепочка для первой отрисовки страницы:
HTML → DOM → Render Tree → Layout → Paint
CSS → CSSOM ↗Оптимизация CRP:
<head> (чтобы не блокировать рендеринг после парсинга)<body> или с defer<link rel="preload"> для критических ресурсов**Начни с общей картины**: "Загрузка страницы проходит в несколько этапов: сеть, парсинг, рендеринг". Это показывает системное мышление.
**Обязательно упомяни**: блокирующий характер синхронных скриптов и CSS — это ключевой момент для оптимизации. Разницу между DOMContentLoaded и load события.
**Хорошее дополнение**: упомяни Critical Rendering Path и что ты делал бы для оптимизации (defer, preload, CSS в head).
**Время ответа**: 2-3 минуты. Можно нарисовать схему на доске.
1. **"Браузер просто открывает страницу"** — полное непонимание процесса. Это показывает, что кандидат никогда не занимался оптимизацией.
2. **Путаница DOMContentLoaded и load** — очень частая ошибка, из-за которой возникают баги с "элемент не найден".
3. **Незнание про блокирующие скрипты** — если не знаешь, почему <script> в <head> без defer замедляет загрузку, это серьёзный пробел.
Симуляция последовательности загрузки браузера с временными метками
// Симуляция этапов загрузки браузера
// В реальности эти этапы выполняет движок браузера
function simulateBrowserLoading(url) {
const steps = []
const startTime = Date.now()
function log(step, detail = '') {
const elapsed = Date.now() - startTime
steps.push({ step, detail, ms: elapsed })
console.log(`[${String(elapsed).padStart(4)}ms] ${step}${detail ? ': ' + detail : ''}`)
}
console.log(`=== Загрузка ${url} ===\n`)
// 1. Сеть
log('DNS lookup', 'example.com → 93.184.216.34')
log('TCP Handshake', 'SYN → SYN-ACK → ACK')
log('TLS Handshake', 'HTTPS шифрование')
log('HTTP GET', `GET / HTTP/1.1 Host: ${url}`)
log('HTTP 200 OK', 'Получен HTML (12 KB)')
// 2. Парсинг
log('HTML Parsing', 'Построение DOM-дерева...')
log('CSS Found', 'Найден <link rel="stylesheet"> — блокирует рендеринг!')
log('CSS Parsing', 'Построение CSSOM...')
log('Script Found', 'Найден <script defer> — скачивается параллельно')
log('DOM Ready', 'DOMContentLoaded событие')
// 3. Рендеринг
log('Render Tree', 'DOM + CSSOM объединены')
log('Layout', 'Вычисление позиций и размеров элементов')
log('Paint', 'Отрисовка пикселей')
log('Compositing', 'GPU объединяет слои')
// 4. Финал
log('Script Executed', 'defer-скрипты выполнены')
log('Images Loaded', 'Все ресурсы загружены')
log('load event', 'window.onload вызван')
console.log('\n=== Итоговая последовательность ===')
return steps
}
simulateBrowserLoading('example.com')
// Демонстрация разницы async/defer/обычный
console.log('\n=== Влияние скриптов на парсинг ===')
const scenarios = [
{
type: 'Обычный <script>',
timeline: [
'Парсинг HTML....',
'⏸️ ПАУЗА: скачиваем script.js',
'⏸️ ПАУЗА: выполняем script.js',
'Продолжаем парсинг HTML',
'DOMContentLoaded'
]
},
{
type: '<script async>',
timeline: [
'Парсинг HTML.... (параллельно скачиваем script.js)',
'⏸️ ПАУЗА: выполняем script.js (как только скачался)',
'Продолжаем парсинг HTML',
'DOMContentLoaded'
]
},
{
type: '<script defer>',
timeline: [
'Парсинг HTML.... (параллельно скачиваем script.js)',
'DOMContentLoaded ← скрипт выполняется ЗДЕСЬ',
'Выполняем script.js (после полного парсинга)',
]
}
]
for (const scenario of scenarios) {
console.log(`\n${scenario.type}:`)
scenario.timeline.forEach((step, i) => {
console.log(` ${i + 1}. ${step}`)
})
}Расставь этапы загрузки браузера в правильном порядке. Реализуй функцию sortLoadingSteps(steps), которая принимает массив этапов в случайном порядке и возвращает их в правильной последовательности. Этапы: "DNS lookup", "TCP Handshake", "HTTP Request", "HTML Parsing", "CSS Parsing", "Render Tree", "Layout", "Paint", "Compositing". Используй числовой массив correctOrder для определения порядка.
Для сортировки используй LOADING_STAGES[a] - LOADING_STAGES[b]. После HTML Parsing стреляет DOMContentLoaded. HTML Parsing блокирует синхронный <script>.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке