From 49495826c0647df81ed311f1b4d83bbf8f37b7d2 Mon Sep 17 00:00:00 2001 From: Faustze Date: Fri, 24 Jul 2026 15:33:42 +0300 Subject: [PATCH 1/2] sync: update vault notes Rename in Obsidian-Vault: browser/Event Loop/Event Loop.md -> Event Loop, Microtasks, Macrotasks.md. Updates the two index pages and Rendering Pipeline.md that link to it. --- content-ru/.translation-manifest.json | 12 +-- .../Event Loop, Microtasks, Macrotasks.md | 88 +++++++++++++++++ content-ru/browser/Event Loop/Event Loop.md | 93 ------------------ .../browser/Event Loop/Rendering Pipeline.md | 97 +++++++++---------- content-ru/browser/Event Loop/index.md | 6 +- content-ru/browser/index.md | 18 ++-- ... => Event Loop, Microtasks, Macrotasks.md} | 0 .../browser/Event Loop/Rendering Pipeline.md | 2 +- content/browser/Event Loop/index.md | 2 +- content/browser/index.md | 2 +- 10 files changed, 155 insertions(+), 165 deletions(-) create mode 100644 content-ru/browser/Event Loop/Event Loop, Microtasks, Macrotasks.md delete mode 100644 content-ru/browser/Event Loop/Event Loop.md rename content/browser/Event Loop/{Event Loop.md => Event Loop, Microtasks, Macrotasks.md} (100%) diff --git a/content-ru/.translation-manifest.json b/content-ru/.translation-manifest.json index 5f437b0190c1..8d0fd1d0eda6 100644 --- a/content-ru/.translation-manifest.json +++ b/content-ru/.translation-manifest.json @@ -1,15 +1,15 @@ { - "browser/Event Loop/Event Loop.md": "ef7e18bdfa11924dff151a6a5557be2893e4004c9c10068ffb553f0008c36225", - "browser/Event Loop/Rendering Pipeline.md": "b698d9525fdb3aea1de4b3243b52c8bc411ad011ace105e9388a18ea704192d6", - "browser/Event Loop/index.md": "00cc102dabdebcb267cbfa6c0f10ca45571ecb015de4a2ac221857421f2e9db4", + "browser/Event Loop/Rendering Pipeline.md": "272e73f40aadfd7fbe43603dc75f044beee1672196e992f76f240937a0cb8bd6", + "browser/Event Loop/Event Loop, Microtasks, Macrotasks.md": "ef7e18bdfa11924dff151a6a5557be2893e4004c9c10068ffb553f0008c36225", "browser/Network/CORS.md": "90a34e5d48b1faad3fdb74ecb7b41fa0b78bb87af7e157c439f2c537d60632a8", "browser/Network/Cookies & Storage.md": "9a49a4e936342471acae3b88e5f5f81f9c2c7c83e14be4a19dbe3acb88330d87", + "browser/Event Loop/index.md": "f9f7836aceeba96e0518a91f66bb916e1d194bc7e52f58ba2e8684a643866840", "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", + "browser/index.md": "2eb4623fd4287396fe34aa7ce75076ae5e30f88af39c8a4e3eab03b3530ebab9", "headhunter/index.md": "d9888f62e5b1a3e71f67b05616d51e15e1eccb67be651c33da850f48f438afd2", "index.md": "9da5f03310750282a9d78c77d9f3d8bd547ca447608377e930b344b05465465a", "leetcode/Array/11-container-with-most-water.md": "8a735550d14dee7e853dee42c6ba6fd04b8671a31b76958370e192972cb39d5a", @@ -99,6 +99,6 @@ "leetcode/index.md": "88c151c7c5564227dea91e47d96076632d854dd7fc1eddfa45440093be401dc3", "vue/Internals/Compiler.md": "c46b3dd12912b11bc4f0a34b2abba574baff9626f42df410eab279ae77b99e90", "vue/Internals/Reactivity.md": "9b502f513195b4512f4d8188f1ac30746030e1c5a52a6a626d51c80c15752d24", - "vue/index.md": "aa6244e7ef392b574ddd86f1cafeeac49e03dda7df6eb83b1767e493d1059669", - "vue/Internals/index.md": "0637645f58c346554c1687702236afa74ed3b25e795a4c0ad5038369ed0479fb" + "vue/Internals/index.md": "0637645f58c346554c1687702236afa74ed3b25e795a4c0ad5038369ed0479fb", + "vue/index.md": "aa6244e7ef392b574ddd86f1cafeeac49e03dda7df6eb83b1767e493d1059669" } diff --git a/content-ru/browser/Event Loop/Event Loop, Microtasks, Macrotasks.md b/content-ru/browser/Event Loop/Event Loop, Microtasks, Macrotasks.md new file mode 100644 index 000000000000..e86cc23d8130 --- /dev/null +++ b/content-ru/browser/Event Loop/Event Loop, Microtasks, Macrotasks.md @@ -0,0 +1,88 @@ +# ⏱ Event Loop, микротаски, макротаски, requestAnimationFrame + +## 💡 Основная идея + +> Event Loop не выполняет код. Это диспетчер: он проверяет, пуст ли стек вызовов, и затем перемещает следующую задачу из очереди в него. + +Два вопроса, на которые отвечает эта заметка: почему `setTimeout(fn, 0)` всегда выполняется после всего синхронного кода *и* всех промисов? И почему бесконечная цепочка `Promise.then()` намертво замораживает вкладку, а бесконечный цикл `requestAnimationFrame` — нет? + +--- +# 🧭 Участники + +1. **Call Stack (стек вызовов)** — выполняет синхронный JS построчно. Пока он не пуст, ничего другого не выполняется. +2. **JS-движок (V8 и т.д.)** — выполняет только стек вызовов. У него нет встроенного понятия `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** — сам диспетчер: в цикле проверяет «пуст ли стек вызовов?», извлекает следующую задачу, помещает её в стек. + +--- +# 🔑 Золотое правило + +> После каждой синхронной задачи Event Loop **полностью опустошает** очередь микротасков — включая микротаски, добавленные во время самого опустошения. Только когда очередь пуста, он переходит дальше (к рендерингу или к одному макротаску). + +Разница не в «приоритете» — а в разной стратегии опустошения: микротаски означают *«заверши всё, что накопилось, сколько бы там ни оказалось»*; макротаск означает *«возьми ровно одну вещь и верни управление диспетчеру»*. + +**Одна полная итерация цикла:** + +``` +Call Stack (весь синхронный код) + → полностью опустошить очередь микротасков (даже если она пополняется во время опустошения) + → колбэки requestAnimationFrame (снимок ровно этого кадра) + → Render: 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 для кадра — это снимок (батч), зафиксированный в начале обработки этого кадра.** Всё, что регистрируется через `requestAnimationFrame(...)` *во время* выполнения текущего батча, физически не может попасть в этот же батч — только в следующий кадр: + +```js +function animate() { + x++; + requestAnimationFrame(animate); // попадёт в СЛЕДУЮЩИЙ кадр, не в этот +} +requestAnimationFrame(animate); +// Render (Layout → Paint → Composite) гарантированно происходит между кадрами +``` + +Поэтому бесконечная рекурсия через rAF не блокирует браузер: рендер всегда происходит между итерациями. Ключевое различие не в том, насколько быстро выполняется каждый колбэк — а в том, что у очереди микротасков нет границы-снимка, а у батча rAF она есть. + +--- +# 🧠 Одним предложением + +> Условие выхода Event Loop для очереди микротасков — «пусто», а самопополняющаяся очередь никогда его не достигает. Очередь rAF же выходит через *снимок*: всё, что добавляется во время батча, откладывается на следующий, независимо от скорости выполнения. + +[[browser/Event Loop/index|Event Loop & Rendering]] +[[browser/Event Loop/Rendering Pipeline|Rendering Pipeline — что происходит после опустошения очереди микротасков]] +#browser diff --git a/content-ru/browser/Event Loop/Event Loop.md b/content-ru/browser/Event Loop/Event Loop.md deleted file mode 100644 index 91139d336c39..000000000000 --- a/content-ru/browser/Event Loop/Event Loop.md +++ /dev/null @@ -1,93 +0,0 @@ -# ⏱ 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 diff --git a/content-ru/browser/Event Loop/Rendering Pipeline.md b/content-ru/browser/Event Loop/Rendering Pipeline.md index c14c89913134..a2ce209900f5 100644 --- a/content-ru/browser/Event Loop/Rendering Pipeline.md +++ b/content-ru/browser/Event Loop/Rendering Pipeline.md @@ -2,115 +2,106 @@ ## 💡 Основная идея -> Не каждое изменение стиля одинаково дорого. То, что триггерит CSS-свойство — 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) +DOM + CSSOM → Render Tree (исключает display:none) +Render Tree → Layout (геометрия: x, y, width, height) +Layout → Paint (пиксели: цвет, текст, тени) +Paint → Composite (GPU собирает слои) ``` -`element.style.width = '100px'` **не** триггерит layout немедленно. Браузер просто помечает элемент (и часто его поддерево) как «грязный» и откладывает пересчёт до следующей естественной точки рендера — шага, который идёт сразу после того, как опустошается очередь микротасков и отрабатывают колбэки rAF в [[browser/Event Loop/Event Loop|Event Loop]]. +`element.style.width = '100px'` **не** триггерит layout немедленно. Браузер просто помечает элемент (и часто его поддерево) как "грязный" и откладывает пересчёт до следующей естественной точки рендера — шага, который идёт сразу после того, как очередь микрозадач опустеет и отработают колбэки rAF в [[browser/Event Loop/Event Loop|Event Loop]]. -Важное различие: **CSSOM** (распарсенные правила из `