Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions content-ru/.translation-manifest.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
{
"browser/Event Loop/Event Loop.md": "ef7e18bdfa11924dff151a6a5557be2893e4004c9c10068ffb553f0008c36225",
"browser/Event Loop/Rendering Pipeline.md": "b698d9525fdb3aea1de4b3243b52c8bc411ad011ace105e9388a18ea704192d6",
"browser/Event Loop/index.md": "00cc102dabdebcb267cbfa6c0f10ca45571ecb015de4a2ac221857421f2e9db4",
"browser/Network/CORS.md": "90a34e5d48b1faad3fdb74ecb7b41fa0b78bb87af7e157c439f2c537d60632a8",
"browser/Network/Cookies & Storage.md": "9a49a4e936342471acae3b88e5f5f81f9c2c7c83e14be4a19dbe3acb88330d87",
"browser/Network/HTTP Headers & Caching.md": "84d40df2788021f2466cb63770346463df8cab99283ec7f78ed57026ee035c09",
"browser/Network/TCP & TLS Handshake.md": "40b29f2a89a82ff105ec0b9e581a3b0bf7b6d0b27937deebb636225c2fd8db16",
"browser/Network/index.md": "8562b0aabb62de7630dcf0b354e7c903792255721a1c57f519b4f1245651e483",
"browser/index.md": "36cb2ffc1ebf165c0ceabc5aab8c514d292f06d148fdb359ac03da0a14964313",
"headhunter/JavaScript-easy-level.md": "07d772fee0195708ac9b6bdf41e1f0fcd2efcf15861cc34947dbce0975d26277",
"headhunter/JavaScript-middle-level.md": "46ae57febadc70bebdc4f8116a7cf2db901c525e2b573bcfa34377be7af45ac0",
"headhunter/index.md": "d9888f62e5b1a3e71f67b05616d51e15e1eccb67be651c33da850f48f438afd2",
Expand Down Expand Up @@ -90,6 +99,6 @@
"leetcode/index.md": "88c151c7c5564227dea91e47d96076632d854dd7fc1eddfa45440093be401dc3",
"vue/Internals/Compiler.md": "c46b3dd12912b11bc4f0a34b2abba574baff9626f42df410eab279ae77b99e90",
"vue/Internals/Reactivity.md": "9b502f513195b4512f4d8188f1ac30746030e1c5a52a6a626d51c80c15752d24",
"vue/Internals/index.md": "0637645f58c346554c1687702236afa74ed3b25e795a4c0ad5038369ed0479fb",
"vue/index.md": "aa6244e7ef392b574ddd86f1cafeeac49e03dda7df6eb83b1767e493d1059669"
"vue/index.md": "aa6244e7ef392b574ddd86f1cafeeac49e03dda7df6eb83b1767e493d1059669",
"vue/Internals/index.md": "0637645f58c346554c1687702236afa74ed3b25e795a4c0ad5038369ed0479fb"
}
93 changes: 93 additions & 0 deletions content-ru/browser/Event Loop/Event Loop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# ⏱ Event Loop, микротаски, макротаски, requestAnimationFrame

## 💡 Основная идея

> Event Loop не выполняет код. Это диспетчер: он проверяет, пуст ли call stack, и затем перекладывает в него следующую задачу из очереди.

На два вопроса отвечает эта заметка: почему `setTimeout(fn, 0)` всегда выполняется после всего синхронного кода _и_ всех промисов? И почему бесконечная цепочка `Promise.then()` намертво замораживает вкладку, а бесконечный цикл `requestAnimationFrame` — нет?

---

# 🧭 Участники

1. **Call Stack** — выполняет синхронный JS строку за строкой. Пока он не пуст, ничего другого не выполняется.
2. **JS-движок (V8 и т.д.)** — выполняет только call stack. У него нет встроенного понятия `setTimeout`, DOM или `fetch` — ничего из этого не входит в спецификацию ECMAScript.
3. **Web API** (браузер) / **libuv** (Node.js) — отдельная часть рантайма, которая непосредственно считает таймеры, слушает сетевые/DOM-события и готовит кадры.
4. **Очередь макротасков** — `setTimeout`, `setInterval`, DOM-события, I/O. FIFO; Event Loop забирает из неё **по одной задаче за раз**.
5. **Очередь микротасков** — `Promise.then`, `queueMicrotask`, `MutationObserver`. FIFO, но Event Loop **опустошает её полностью**, прежде чем заняться чем-либо ещё.
6. **Event Loop** — сам диспетчер: в цикле проверяет «пуст ли call stack?», забирает следующую задачу, помещает её в стек.

---

# 🔑 Золотое правило

> После каждой синхронной задачи Event Loop **полностью опустошает** очередь микротасков — включая те, что добавляются во время самого опустошения. И только когда очередь пуста, он переходит дальше (к рендерингу или к одному-единственному макротаску).

Разница не в «приоритете» — это разная стратегия опустошения: микротаски означают _«доделать всё, что накопилось, сколько бы его ни оказалось»_; макротаск означает _«взять ровно одну вещь и вернуть управление диспетчеру»_.

**Одна полная итерация цикла:**

```
Call Stack (весь синхронный код)
→ опустошить очередь микротасков ПОЛНОСТЬЮ (даже если она пополняется в процессе)
→ колбэки requestAnimationFrame (снимок ровно этого кадра)
→ Рендер: Layout → Paint → Composite
→ взять ОДИН макротаск → выполнить его
→ снова опустошить очередь микротасков
→ ... повторить
```

---

# 🎞 requestAnimationFrame

`requestAnimationFrame(cb)` просит браузер _«выполнить это прямо перед отрисовкой следующего кадра»_. Это не таймер, и он не даёт гарантий по точным миллисекундам — он привязан к реальному циклу обновления экрана (~60/сек).

Порядок выполнения с учётом rAF:

```js
Promise.resolve().then(() => console.log("promise"))
requestAnimationFrame(() => console.log("raf"))
setTimeout(() => console.log("timeout"), 0)

// promise → raf → timeout
```

---

# ❄️ Почему бесконечный Promise.then() замораживает вкладку, а бесконечный rAF — нет

Это не совпадение — обе очереди работают по принципиально разной механике.

**Очередь микротасков проверяется динамически.** Перед каждым шагом Event Loop спрашивает «пуста ли очередь?». Если выполняющийся сейчас микротаск ставит в очередь новый, тот присоединяется к очереди и выполняется **в этом же проходе**. Для бесконечной цепочки условие выхода («пусто») никогда не наступает:

```js
function loop() {
Promise.resolve().then(loop) // каждый .then() порождает следующий
}
loop()
// Event Loop никогда не доходит до рендеринга, макротасков или кликов — вкладка мертва
```

**Очередь rAF для кадра — это снимок (batch), зафиксированный в начале обработки этого кадра.** Всё, что регистрируется через `requestAnimationFrame(...)` _во время_ выполнения текущего batch, физически не может попасть в этот же batch — только в следующий кадр:

```js
function animate() {
x++
requestAnimationFrame(animate) // попадёт в СЛЕДУЮЩИЙ кадр, не в этот
}
requestAnimationFrame(animate)
// Рендер (Layout → Paint → Composite) гарантированно происходит между кадрами
```

Поэтому бесконечная рекурсия через rAF не блокирует браузер: рендер гарантированно вклинивается между итерациями. Ключевое отличие не в том, насколько быстро выполняется каждый из колбэков — а в том, что у очереди микротасков нет границы-снимка, а у batch'а rAF она есть.

---

# 🧠 Одним предложением

> Условие выхода Event Loop для очереди микротасков — «пусто», а самопополняющаяся очередь никогда его не достигает. Очередь rAF, напротив, выходит по _снимку_: всё, что добавляется во время batch'а, откладывается на следующий, независимо от того, насколько быстро он выполняется.

[[browser/Event Loop/index|Event Loop & Rendering]]
[[browser/Event Loop/Rendering Pipeline|Rendering Pipeline — что происходит после опустошения очереди микротасков]]
#browser
116 changes: 116 additions & 0 deletions content-ru/browser/Event Loop/Rendering Pipeline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# 🖼 Rendering Pipeline: Parse → Style → Layout → Paint → Composite

## 💡 Основная идея

> Не каждое изменение стиля одинаково дорого. То, что триггерит CSS-свойство — Layout, Paint или просто Composite — определяет, будет ли анимация плавной или начнёт дропать кадры.

---

# 🧭 Полный пайплайн

```
Parse HTML → DOM
Parse CSS → CSSOM
DOM + CSSOM → Render Tree (excludes display:none)
Render Tree → Layout (geometry: x, y, width, height)
Layout → Paint (pixels: color, text, shadows)
Paint → Composite (GPU assembles layers)
```

`element.style.width = '100px'` **не** триггерит layout немедленно. Браузер просто помечает элемент (и часто его поддерево) как «грязный» и откладывает пересчёт до следующей естественной точки рендера — шага, который идёт сразу после того, как опустошается очередь микротасков и отрабатывают колбэки rAF в [[browser/Event Loop/Event Loop|Event Loop]].

Важное различие: **CSSOM** (распарсенные правила из `<style>`/стилшитов) и инлайновый `element.style` (объект `CSSStyleDeclaration`, привязанный к атрибуту `style` одного элемента) — это разные объекты. Запись `el.style.width = ...` никогда не трогает CSSOM — она только помечает грязным этот один элемент.

---

# ⚡ Что триггерит каждый этап

```
Layout: width/height, top/left/right/bottom, margin/padding,
border-width, font-size, display, adding/removing DOM nodes
Paint: color, background, box-shadow, border-radius, visibility, outline
Composite-only: transform, opacity
```

## Почему transform/opacity дешёвые

Не потому что _значат_ эти свойства — а из-за того, _где_ они обрабатываются. Если у элемента уже есть свой compositor layer (GPU-текстура — от `will-change`, идущей анимации, `position: fixed`, `<video>`, `<canvas>` и т.д.), изменение `transform`/`opacity` — это просто применение матрицы трансформации или значения альфа-канала к _уже растеризованной_ текстуре, целиком на **compositor thread**. Layout и Paint вообще не запускаются.

- `transform` — это не категория «свойств для анимации», это геометрические трансформации (translate/scale/rotate/skew/matrix), которые оказались дешёвыми в анимации.
- `opacity` — это не яркость, это альфа-канал (0 = невидимо, 1 = непрозрачно).

Все функции `transform` компилируются в одну каноническую форму:

- `translate(x, y)` — сдвиг по осям (`translateX/Y/Z`, `translate3d`)
- `scale(x, y)` — изменение размера (`scale(1.5)` = в 1.5 раза больше); `scaleX`/`scaleY` независимо по каждой оси
- `rotate(angle)` — вращение вокруг точки (по умолчанию: центр элемента); `rotateX/Y/Z` для 3D
- `skew(x-angle, y-angle)` — сдвиг/скос: превращает прямоугольник в параллелограмм
- `matrix(a, b, c, d, e, f)` — низкоуровневая матрица 2×3 affine, в которую компилируется всё вышеперечисленное (`matrix3d` — 4×4, для 3D)

---

# 🧵 Main thread vs Compositor thread

```
Main thread: JS execution → Style calc → Layout → Paint
Compositor thread: Composite (transform/opacity)
```

Main thread — это место, где живут [[browser/Event Loop/Event Loop|Event Loop]], call stack и весь JS — Style, Layout и Paint тоже находятся там. Если main thread заблокирован (долгий синхронный JS, forced reflow), всё замирает: скролл, клики и любая анимация на `width`/`top`.

Compositor thread отдельный и не блокируется main thread'ом. К моменту выполнения Composite DOM/CSSOM уже не важны: Paint (на main thread) уже превратил элемент в растеризованную GPU-текстуру. Compositor thread видит только готовые текстуры и матрицы трансформаций — он никогда не трогает дерево DOM. Composite — это не «рисование поверх HTML/CSS-скелета», это «размещение уже готовых картинок на экране».

## Как формируются compositor layers

Слой — это не DOM-элемент, а область пикселей плюс правило её позиционирования. Он состоит из:

- **display list** — записанных Paint команд рисования («залить этот прямоугольник», «нарисовать этот текст») для части DOM в этом слое
- **растеризованной текстуры (bitmap)** — результата выполнения display list, обычно нарезанной на **тайлы** (например, 256×256px) для эффективной перезагрузки
- **свойств трансформации** — матрицы (transform), opacity и позиции в системе координат compositor'а

Этапы формирования:

1. **Style** — вычисляются значения CSS
2. **Layout** — вычисляется геометрия (размеры, позиции)
3. **Построение дерева слоёв (layerization)** — main thread решает, какие элементы получают собственный compositor layer. Триггеры: анимированные `transform`/`opacity`, `will-change`, `position: fixed`, `<video>`, `<canvas>`. Всё остальное рисуется в слой родителя.
4. **Paint** — main thread не рисует пиксели, он записывает display list для каждого слоя
5. **Растеризация** — display list превращается в реальные пиксели, обычно через GPU (например, GPU-бэкенд Skia), часто на отдельных raster-потоках
6. **Compositing** — compositor thread собирает готовые текстуры слоёв в финальный кадр, применяя transform/opacity каждого слоя целиком

Шаги 1–4 выполняются на main thread; шаги 5–6 — на отдельных потоках. Если у элемента, который уже имеет слой, меняется только `transform`/`opacity`, шаги 1–4 полностью пропускаются — сразу к шагу 6 с существующей текстурой.

Почему каждый слой рендерится в свою текстуру: её можно переиспользовать без перерисовки. Если слой уже содержит что-то дорогое (текст, тени, градиенты) и его просто перемещают (`translateX`), нет нужды заново выполнять команды рисования — GPU просто перепозиционирует готовую текстуру. Это также распараллеливает работу: разные слои растеризуются одновременно на разных потоках, и перерисовывается только тот слой, который реально изменился.

---

# 🐢 Forced synchronous layout

Если вы записываете стиль и сразу читаете свойство, зависящее от layout (`offsetWidth`, `getBoundingClientRect()`, `clientHeight`, `scrollTop`, …), браузер обязан пересчитать геометрию **синхронно, прямо на месте**, чтобы дать точный ответ — он не может отложить это до следующей точки рендера.

```js
// bad: 1000 forced layouts, one per iteration
items.forEach((item) => {
item.style.width = "100px" // write — flags the element dirty
console.log(item.offsetWidth) // read — forces a full recalculation NOW
})

// good: one layout for the whole batch
items.forEach((item) => {
item.style.width = "100px"
}) // all writes first
items.forEach((item) => {
console.log(item.offsetWidth)
}) // all reads — first read recomputes, the rest hit the cache
```

Почему это дорого: layout вычисляет геометрию не только для затронутого элемента — обычно для значительной затронутой части дерева (элементы влияют друг на друга через flexbox, проценты, реflow текста). Это как заново измерять 1000 стен и звать инспектора после каждой, вместо того чтобы позвать его один раз в конце.

---

# 🧠 Одним предложением

> Изменения `width`/`top` стоят Layout + Paint на main thread; изменения `color`/`shadow` стоят только Paint; `transform`/`opacity` пропускают и то, и другое и остаются на compositor thread — это и есть вся причина предпочитать анимировать именно их.

[[browser/Event Loop/index|Event Loop & Rendering]]
[[browser/Event Loop/Event Loop|Event Loop — where render fits into the loop]]
#browser
Loading