diff --git a/content-ru/.translation-manifest.json b/content-ru/.translation-manifest.json index 531e2093a967..5f437b0190c1 100644 --- a/content-ru/.translation-manifest.json +++ b/content-ru/.translation-manifest.json @@ -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", @@ -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" } diff --git a/content-ru/browser/Event Loop/Event Loop.md b/content-ru/browser/Event Loop/Event Loop.md new file mode 100644 index 000000000000..91139d336c39 --- /dev/null +++ b/content-ru/browser/Event Loop/Event Loop.md @@ -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 diff --git a/content-ru/browser/Event Loop/Rendering Pipeline.md b/content-ru/browser/Event Loop/Rendering Pipeline.md new file mode 100644 index 000000000000..c14c89913134 --- /dev/null +++ b/content-ru/browser/Event Loop/Rendering Pipeline.md @@ -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** (распарсенные правила из `