Mundarija (36)
- Bu darsda
- 1. Nega bu kerak?
- 2. Konveyer — JavaScript nuqtai nazaridan
- 2.1 Besh qadam
- 2.2 Brauzer dangasa — va bu yaxshi
- 2.3 Qaysi o'zgarish qaysi bosqichni uyg'otadi
- 3. Majburiy reflow
- 3.1 O'qish dangasalikni buzadi
- 3.2 Thrashing: sikl ichida
- 3.3 Qaysi o'qishlar layout'ni majburlaydi
- 3.4 Iflos bo'lmasa — majburlash ham yo'q
- 4. O'lchaymiz
- 4.1 O'zingiz sinab ko'ring
- 4.2 Avtomatik aniq o'lchov
- 4.3 Performance yozuvida qanday ko'rinadi
- 5. Davolash usullari
- 5.1 Avval o'qish, keyin yozish
- 5.2 Yozishni keyingi kadrga qo'yish
- 5.3 O'lchamni so'ramang — xabar kuting
- 5.4 Animatsiyada — transform va opacity
- 5.5 will-change — JavaScript qo'yadi va olib tashlaydi
- 5.6 Layout maydonini kichraytirish
- 6. Ko'p uchraydigan xatolar
- 6.1 Sikl ichida o'lcham o'qish
- 6.2 Yashirin o'qishlar
- 6.3 Funksiya ichida yashiringan o'qish
- 6.4 Bir martalik o'lchovga ishonish
- 6.5 Har narsani transform ga aylantirish
- 7. Mashqlar
- 1-mashq (oson): Thrashing qaysi birida?
- 2-mashq (o'rta): Kartalarni tenglang
- 3-mashq (qiyin): Mini FastDOM
- 4-mashq: Amaliy tajriba — Performance panelida Forced reflow
- 8. Real ishda
- Xulosa
- Manbalar
Rendering pipeline va layout thrashing: JavaScript brauzerni qachon majburan "o'lchatadi"
Qisqacha: Brauzer DOM o'zgarishini darhol hisoblamaydi — "iflos" deb belgilab, kadr chizishdan oldin bir marta style → layout → paint → composite qiladi. Lekin JavaScript o'zgarishdan keyin o'lcham o'qisa (
offsetHeight,getBoundingClientRect(),innerText), brauzer layout'ni shu zahoti qilishga majbur bo'ladi — bu majburiy reflow. Sikl ichida "yoz — o'qi — yoz — o'qi" — layout thrashing: 500 ta kartada 500 ta layout. Davosi: avval hamma o'qishlar, keyin hamma yozishlar; animatsiyadatransformvaopacity.
Bu darsda
- Brauzer konveyerining to'rt bosqichini JavaScript nuqtai nazaridan tushuntira olasiz: qaysi o'zgarish qaysi bosqichni uyg'otadi.
- Qaysi o'qishlar layout'ni majburlashini bilasiz va ro'yxatni o'zingiz tekshira olasiz.
- Layout thrashing'ni Chrome'da o'lchab, Performance yozuvida topa olasiz.
- O'qish va yozishni guruhlash,
requestAnimationFrame, kuzatuvchilar vatransformbilan uni tuzata olasiz.
Oldin bilishingiz kerak: Rendering pipeline va animatsiya samaradorligi, Ko'p elementni samarali chizish, Element o'lchamlari va scroll, requestAnimationFrame, Performansni o'lchash.
1. Nega bu kerak?
Web Components bilan «Bahor» menyusi chiroyli bo'ldi. Endi Jasur aka yangi narsa so'radi: menyu sahifasida kartalar bir qatorda turganda, balandligi har xil bo'lib qolyapti — biri qisqa tavsifli, biri uzun. "Hammasi bir xil balandlikda bo'lsin".
Sardor tez yozdi: har bir kartaga eng baland kartaning balandligini beradigan funksiya. Kompyuterida ishladi. Jasur akaning arzon telefonida esa «Tenglash» bosilganda sahifa yarim soniya qotib qoldi. Kartalar 500 ta ham emas edi.
Kod "to'g'ri" edi. Muammo — u brauzerni qanday ishlatganida. Sardorning siklida har kartaga yozish va o'lcham o'qish navbatma-navbat kelardi. Har o'qishda brauzer butun sahifaning joylashuvini qaytadan hisoblashga majbur bo'ldi.
Bu hodisani oldin ham uchratdik: Renderni nima to'xtatadi darsida qoida sifatida, Ko'p elementni samarali chizish darsida birinchi o'lchov bilan, requestAnimationFrame darsida esa "yozishni kadrga qo'yish" usuli bilan. Bugun hammasini bir joyga yig'amiz: brauzer ichida aniq nima bo'ladi, qaysi o'qishlar xavfli, buni qanday o'lchaymiz va qanday tuzatamiz.
2. Konveyer — JavaScript nuqtai nazaridan
2.1 Besh qadam
Rendering pipeline darsida konveyerni CSS tomonidan ko'rdik. JavaScript bilan birga u besh qadamdan iborat:
flowchart LR
J["JavaScript<br/>DOM'ni o'zgartiradi"] --> S["Style<br/>qaysi qoida qayerga"]
S --> L["Layout<br/>o'lcham va joy"]
L --> P["Paint<br/>piksellar ro'yxati"]
P --> C["Composite<br/>qatlamlarni yig'ish"]- JavaScript — skriptingiz DOM'ni yoki stilni o'zgartiradi:
classList.add,style.width,append. - Style (uslub hisobi) — brauzer har bir element uchun qaysi CSS qoidalari ishlashini va yakuniy qiymatlarni hisoblaydi. Chrome yozuvlarida bu bosqich Recalculate Style deb ataladi.
- Layout (joylashtirish) — har elementning o'lchami va sahifadagi joyi. Uning eski nomi — reflow ("qayta oqim"). Ikkalasi bir narsa.
- Paint (bo'yash) — nimani qanday chizish kerakligi ro'yxati: matn, rang, soya. Eski nomi — repaint.
- Composite (yig'ish) — tayyor qatlamlarni ekranda bir-birining ustiga qo'yish. Ko'pincha videokarta bajaradi.
Bosqich nomlarini inglizcha eslab qoling: DevTools'da ular aynan shunday yoziladi.
2.2 Brauzer dangasa — va bu yaxshi
Muhim g'oya: card.style.width = "301px" yozilganda brauzer hech narsani hisoblamaydi. U faqat belgi qo'yadi: "bu element uslubi eskirdi, layout ham eskirdi". Bu holat iflos (dirty) deyiladi.
Keyin skript ishlashda davom etadi. 500 ta karta o'zgarsa, 500 ta belgi qo'yiladi. Skript tugab, kadr chizish vaqti kelganda brauzer bir marta style va layout qiladi — hamma o'zgarish uchun birdaniga. Xuddi ofitsiant buyurtmalarni bittalab oshxonaga yugurib olib bormasdan, stoldagi hammaning buyurtmasini yozib olib, bir marta borishi kabi.
2.3 Qaysi o'zgarish qaysi bosqichni uyg'otadi
Konveyer zanjir: oldingi bosqich ishlasa, keyingilari ham ishlaydi. Shuning uchun o'zgarishning narxi u qaysi bosqichdan boshlashiga bog'liq:
| JS'da o'zgartirdingiz | Ishlaydigan bosqichlar | Narxi |
|---|---|---|
width, height, top, left, font-size, matn, element qo'shish |
Style → Layout → Paint → Composite | eng qimmat |
color, background, box-shadow |
Style → Paint → Composite | o'rtacha |
transform, opacity |
Style → Composite | eng arzon |
Buni JavaScript animatsiyasida o'lchadik. Bir soniyalik rAF animatsiyasi doirani 300 px suradi: bir marta style.left bilan, bir marta style.transform bilan.
O'lchov uchun Performance yozuvi (trace) oldik. Bu — brauzer bir necha soniya ichida nima qilganining vaqt bo'yicha batafsil jurnali: qaysi skript, qaysi Layout, qaysi Paint, qachon. Xuddi samolyotning parvoz jurnali kabi. DevTools'ning Performance paneli aynan shu jurnalni chizib ko'rsatadi. Chrome 154 dagi natija:
leftbilan — har kadrda bitta Layout va ikkita Paint.transformbilan — butun animatsiya davomida bitta Layout va ikkita Paint. Qolgan hamma kadrlarda faqat Style va Composite.
Kadrlar soni sinovda soniyasiga 100 dan ortiq edi, aniq soni ekranga bog'liq. Farq esa doim bir xil: left — har kadr layout, transform — deyarli hech qachon.
Tekshirib ko'ring: Kartaga sichqoncha kelganda JavaScript uning
backgroundrangini o'zgartiradi. Konveyerning qaysi bosqichlari ishlaydi?box-shadowqo'shilsa-chi?margin-top: 4pxqo'shilsa-chi?
Javob
background — Style, Paint, Composite (layout yo'q: o'lcham o'zgarmadi). box-shadow — xuddi shunday: soya joy egallamaydi, faqat chiziladi. margin-top esa o'lchamga ta'sir qiladi — kartaning o'zi va undan keyingi hamma narsa suriladi. Unda Layout ham ishlaydi, eng qimmat yo'l.
3. Majburiy reflow
3.1 O'qish dangasalikni buzadi
Endi asosiy tuzoq. Brauzer o'zgarishlarni yig'ib, keyin bir marta hisoblamoqchi edi. Lekin skript yozgandan keyin o'lcham so'raydi:
card.style.width = "301px"; // yozish — layout "iflos"
const h = card.offsetHeight; // o'qish — javob hozir kerak!offsetHeight ning to'g'ri javobi yangi layout'ga bog'liq: karta torayganda matn qayta o'raladi, balandlik o'zgaradi. Eski qiymatni berib bo'lmaydi. Brauzerda bitta yo'l qoladi: kadr oxirini kutmasdan, shu zahoti style va layout qilish. Skript bu vaqtda to'xtab turadi.
Bu majburiy sinxron layout (forced synchronous layout) yoki qisqacha majburiy reflow (forced reflow) deyiladi. Bitta majburiy reflow — muammo emas. Muammo — ularning ko'pligi.
3.2 Thrashing: sikl ichida
// ❌ har kartada: yoz → o'qi → yoz → o'qi …
for (const card of cards) {
card.style.width = `${newWidth}px`;
card.dataset.height = card.offsetHeight;
}Birinchi aylanish: yozish → layout iflos → o'qish → majburiy layout. Ikkinchi aylanish: yozish → layout yana iflos → o'qish → yana majburiy layout. 500 ta karta — 500 ta to'liq layout. Bu layout thrashing ("layout'ni titratish").
sequenceDiagram
participant JS as Skript
participant B as Brauzer
JS->>B: 1-karta: width yoz
JS->>B: 1-karta: offsetHeight?
B->>B: Style + Layout (majburiy)
B-->>JS: 48
JS->>B: 2-karta: width yoz
JS->>B: 2-karta: offsetHeight?
B->>B: Style + Layout (yana)
Note over JS,B: … 500 marta3.3 Qaysi o'qishlar layout'ni majburlaydi
Har o'qish ham xavfli emas. Biz Chrome 154 da tekshirdik: sikl ichida 50 ta kartaning kengligini o'zgartirib, keyin bitta xususiyatni o'qidik va brauzerning ichki hisoblagichlarini (LayoutCount, RecalcStyleCount) sanadik:
| O'qish | Majburiy layout (50 dan) |
|---|---|
offsetHeight, offsetTop, clientWidth |
50 |
getBoundingClientRect() |
50 |
scrollTop, window.scrollY |
50 |
innerText |
50 |
getComputedStyle(el).height |
50 |
element.focus(), document.elementFromPoint() |
50 |
getComputedStyle(el).color |
0 (faqat style) |
textContent, className, dataset |
0 |
Ikki narsaga e'tibor bering.
getComputedStyle ikki xil. Rang so'ralsa — brauzerga faqat style kerak, layout emas. Balandlik so'ralsa — layout ham. classList va stillar darsida "getComputedStyle ba'zan sahifani qayta o'lchaydi" degan edik — mana aniq javob: o'lchamga bog'liq xususiyat so'ralganda.
element.focus() — o'qish emas, lekin u ham layout so'raydi: fokus olgan elementni ko'rinadigan joyga aylantirish uchun brauzer uning joyini bilishi kerak.
To'liq ro'yxatni Paul Irish'ning "What forces layout / reflow" maqolasida topasiz — dasturchilar uni ko'p yillardan beri ma'lumotnoma sifatida ishlatadi.
3.4 Iflos bo'lmasa — majburlash ham yo'q
Majburiy layout faqat layout iflos bo'lganda yuz beradi. Buni ikki sinov bilan tekshirdik:
- Sikl ichida faqat rang klassini almashtirib (
classList.toggle("hot")), keyinoffsetHeighto'qidik. Natija: 500 ta majburiy style, lekin 0 ta layout. Rang o'lchamga ta'sir qilmaydi — layout iflos bo'lmadi, brauzer eski o'lchamni bemalol berdi. Bu ancha arzon: taxminan 5 ms. - Sikl boshida hech narsa yozmay, faqat o'qisak — layout bir marta ham majburlanmaydi: oldingi kadrdan qolgan toza layout ishlatiladi.
Demak, xavfli kombinatsiya aniq: layout'ga ta'sir qiladigan yozish + layout'ga bog'liq o'qish, navbatma-navbat.
Tekshirib ko'ring: Sikl ichida
card.classList.add("tanlangan")(u faqatborder-colorni o'zgartiradi) vacard.getBoundingClientRect()keladi. Thrashing bormi?
Javob
Layout thrashing yo'q: border-color o'lchamga ta'sir qilmaydi, layout iflos bo'lmaydi. Lekin har aylanishda majburiy style hisobi bo'ladi — brauzer klass o'zgargach qaysi qoidalar ishlashini tekshirishi kerak. Bu arzonroq, lekin bepul emas. Eng yaxshisi baribir: avval hamma o'qishlar, keyin hamma yozishlar. Agar o'sha klass border-width ni o'zgartirsa — o'lcham o'zgaradi va to'liq thrashing boshlanadi.
4. O'lchaymiz
4.1 O'zingiz sinab ko'ring
Oynada 300 ta karta. «Aralash» — Sardorning sikli: yoz va o'qi navbatma-navbat. «Guruhlangan» — avval hamma balandliklarni o'qiydi, keyin hamma kengliklarni yozadi. Ikkalasi ham ish bir xil:
<style>
body { font: 0.9rem/1.4 system-ui, sans-serif; margin: 1rem; }
#list { height: 9rem; overflow: auto; border: 1px solid #ccc; }
.card { border: 1px solid #c8d6c8; padding: 0.3rem;
margin: 0.2rem; }
button { font: inherit; padding: 0.3rem 0.7rem;
margin: 0.5rem 0.2rem; }
</style>
<button id="mixed" type="button">Aralash</button>
<button id="batched" type="button">Guruhlangan</button>
<p id="result">Tugmani bosing.</p>
<div id="list"></div>
<script>
const list = document.getElementById("list");
for (let i = 1; i <= 300; i++) {
const card = document.createElement("div");
card.className = "card";
card.textContent = `${i}-taom: guruch, sabzi, go'sht, ziravor`;
list.append(card);
}
const cards = [...list.children];
let width = 300;
function mixed() {
for (const card of cards) {
card.style.width = `${width}px`;
card.dataset.height = card.offsetHeight; // majburiy layout
}
}
function batched() {
const heights = cards.map((card) => card.offsetHeight);
cards.forEach((card, i) => {
card.style.width = `${width}px`;
card.dataset.height = heights[i];
});
}
function measure(name, fn) {
width = width === 300 ? 299 : 300; // har safar haqiqiy o'zgarish
const start = performance.now();
fn();
const ms = performance.now() - start;
const text = `${name}: ${ms.toFixed(1)} ms`;
document.getElementById("result").textContent = text;
console.log(text);
}
document.getElementById("mixed")
.addEventListener("click", () => measure("Aralash", mixed));
document.getElementById("batched")
.addEventListener("click", () => measure("Guruhlangan", batched));
</script>Har tugmani bir necha marta bosing. «Aralash» o'nlab millisekund ko'rsatadi, «Guruhlangan» — bir millisekunddan kam. Aniq son kompyuteringizga bog'liq. Bizning sinovda (avtomatik, 5 marta) «Aralash» mediana taxminan 55 ms, «Guruhlangan» taxminan 1 ms chiqdi — farq bir necha o'n baravar.
Qatorma-qator muhimlari:
width === 300 ? 299 : 300— har bosishda kenglik haqiqatan o'zgarsin. Bir xil qiymatni qayta yozsangiz, brauzer layout'ni iflos qilmaydi va o'lchov yolg'on chiqadi.batcheddagiheights— avval hamma o'qishlar, o'zgaruvchiga saqlab. Keyin faqat yozishlar. Sikl ichida hech qanday o'qish qolmadi.measurefunksiyasi faqat skript vaqtini o'lchaydi. «Guruhlangan» da yagona layout skript tugagandan keyin, kadr chizishda bo'ladi — o'lchovga kirmaydi. Lekin u bitta, 300 ta emas. Bu adolatli farq: foydalanuvchi uchun aynan shu muhim.
4.2 Avtomatik aniq o'lchov
Bir marta bosish — tasodifiy son. Performansni o'lchash darsidagi qoida: ko'p marta o'lchab, mediana olish. Biz 500 ta karta bilan har usulni 9 marta bajardik. Har o'lchov oldidan bitta kadr kutdik, shunda layout toza holatdan boshlandi. Brauzerning ichki hisoblagichlarini (LayoutCount — nechta layout bo'lgani) avtomatik sinov skriptida page.metrics() orqali oldik. Skript Chrome'ni Node'dan boshqaradi — bunday vositalarni E2E testlarda (22-qism) o'rganasiz:
// sinov skripti (Node 24) — asosiy qismi; page — boshqariladigan tab
const before = await page.metrics();
const ms = await page.evaluate(() => window.mixed());
const after = await page.metrics();
console.log(ms, after.LayoutCount - before.LayoutCount);Natijalar (mediana, 9 o'lchov):
- Aralash500 ta majburiy layout94,8 ms
- Guruhlangan0 ta majburiy layout0,5 ms
- innerText siklda500 ta majburiy layout92,6 ms
- textContent siklda00,3 ms
Manba: O'lchov: Chrome 154 headless, Intel Core i5-12500H, Windows 11, 2026-10-05; har usul 9 marta, mediana
Nimaga qarang:
- Aralash — mediana taxminan 95 ms; boshqa sinov kunlarida 95–130 ms orasida chiqdi. Har o'lchovda aniq 500 ta layout — har bir karta uchun bittadan.
- Guruhlangan — yarim millisekund atrofida, majburiy layout 0. Farq 150 baravardan ortiq.
- innerText va textContent — xuddi shu manzara. Sikl ichida kenglikni o'zgartirib, keyin
innerTexto'qisak — 500 ta layout.textContent— layout'siz.
Arzon telefonni taqlid qilish uchun Chrome'ning protsessorni 4 baravar sekinlashtirish rejimini yoqdik (DevTools'dagi "4x slowdown" bilan bir xil). Unda «Aralash» mediana 550–800 ms ga chiqdi, «Guruhlangan» — taxminan 5 ms. Yarim soniyadan ko'p qotish — Jasur akaning telefonidagi manzara aynan shu.
4.3 Performance yozuvida qanday ko'rinadi
Xuddi shu skript bilan Performance yozuvi ham oldik. «Aralash» bajarilganda trace'da 500 ta Layout hodisasi bor edi. Ularning har birida JavaScript stek izi bor: qaysi funksiyaning qaysi qatori layout'ni chaqirgani. «Guruhlangan» da esa bitta Layout, stek izisiz — u skriptdan keyin, brauzerning o'z navbatida bo'ldi.
DevTools'da xuddi shu narsani ko'rasiz:
- DevTools → Performance → yozishni boshlang → «Aralash» ni bosing → to'xtating.
- Main qatorida bitta uzun vazifa. Uning ichida yuzlab ingichka siyohrang Layout bloklari.
- Vazifa ustida qizil burchak. Pastdagi panelda Forced reflow ogohlantirishi va layout'ni chaqirgan funksiyaga havola —
mixedqatori.
Rendering pipeline darsida "Forced reflow ogohlantirishini chaqiradigan kodni keyinroq yozamiz" degan edik — mana u. Performance panelini to'liq DevTools: Performance, Memory va Network darsida o'rganamiz.
5. Davolash usullari
5.1 Avval o'qish, keyin yozish
Eng oddiy va eng kuchli usul — guruhlash. «Bahor» kartalarini tenglash, to'g'ri yo'l bilan:
function equalizeCards(cards) {
// 1) O'qish — hammasi birga (layout toza bo'lsa, bepul)
const heights = cards.map((card) => card.offsetHeight);
const max = Math.max(...heights);
// 2) Yozish — hammasi birga (layout faqat iflos belgisini oladi)
for (const card of cards) {
card.style.minHeight = `${max}px`;
}
}Bitta nozik joy. Ko'p elementni samarali chizish darsida "avval yozing, keyin bir marta o'qing" degan edik. Renderni nima to'xtatadi darsida esa "avval hamma o'qishlar, keyin hamma yozishlar". Qarama-qarshi emasmi? Yo'q — asosiy qoida bitta: aralashtirmang. "Avval yozish, keyin bitta o'qish" — bitta majburiy layout. "Avval o'qish, keyin yozish" — nol majburiy layout: o'qishlar oldingi kadrdan qolgan toza layout'ni ishlatadi. Imkon bo'lsa, ikkinchisi yaxshiroq.
Aytgancha, kartalarni tenglash uchun JavaScript umuman kerak emas: CSS Grid'da bir qatordagi kartalar o'zi bir xil balandlikda bo'ladi. Eng yaxshi layout kodi — yozilmagan kod.
5.2 Yozishni keyingi kadrga qo'yish
Katta ilovada o'qish va yozish turli modullarda bo'ladi. Ularni qo'lda tartiblash qiyin. requestAnimationFrame darsidagi usul: o'qish hozir, yozish — keyingi kadr boshida:
const heights = cards.map((card) => card.offsetHeight); // hozir
requestAnimationFrame(() => {
cards.forEach((card, i) => {
card.dataset.height = heights[i];
card.style.width = "50%";
}); // keyingi kadr boshida — keyin bitta layout
});Bu g'oyani tizimlashtirgan kutubxona bor — FastDOM. U ikki navbat yuritadi: "o'lchash" va "o'zgartirish". Kadr boshida avval hamma o'lchashlarni, keyin hamma o'zgartirishlarni bajaradi. Uning soddalashtirilgan versiyasini «Mini FastDOM» mashqida o'zingiz yozasiz.
Diqqat: rAF callback'i ichida ham qoida amal qiladi. Callback avval yozib, keyin o'qisa — majburiy reflow shu yerda. rAF faqat "qachon" degan savolga javob beradi, "qanday tartibda" degan savolni siz hal qilasiz.
5.3 O'lchamni so'ramang — xabar kuting
Ba'zan o'lchamni har safar o'qishning hojati yo'q. Brauzerning o'zi layout'dan keyin xabar beradi:
ResizeObserver— element o'lchami o'zgarganda, layout tugagach, yangi o'lchamni beradi.resizehodisasidaoffsetWidtho'qishning o'rniga.IntersectionObserver— element ko'rinish maydoniga kirganini aytadi.scrollhodisasidagetBoundingClientRect()o'qishning o'rniga.
Ikkalasi ham callback'ni tayyor raqamlar bilan, layout'ni majburlamasdan chaqiradi. Scroll paytida har kadrda o'lcham o'qish — thrashing'ning eng keng tarqalgan manbalaridan biri.
O'zgarmaydigan o'lchamni esa bir marta o'qib, keshlang (o'zgaruvchiga saqlang). Masalan, sarlavha balandligi sahifa davomida o'zgarmasa — uni har scroll da qayta o'qish shart emas.
5.4 Animatsiyada — transform va opacity
JavaScript'dan harakat qilsangiz, left/top/width o'rniga transform ishlating. «Qaysi o'zgarish qaysi bosqichni uyg'otadi» bo'limidagi o'lchovni eslang: left har kadrda layout, transform — butun animatsiyada bitta.
Ko'p xususiyatni birma-bir yozish o'rniga bitta klass almashtirish ham foydali: card.classList.add("ochiq"). Brauzer uchun farq kichik (yozishlar baribir yig'iladi), lekin kod tozaroq va o'qish bilan aralashib ketish ehtimoli kam.
5.5 will-change — JavaScript qo'yadi va olib tashlaydi
Rendering pipeline darsida will-change ni ko'rdik: "brauzer, bu element tez orada harakatlanadi — oldindan alohida qatlam tayyorla". O'shanda "animatsiya tugagach o'chirish — JavaScript ishi" degan edik. Mana u:
const panel = document.querySelector(".savat-panel");
function openPanel() {
panel.style.willChange = "transform"; // oldindan ogohlantirish
requestAnimationFrame(() => {
panel.classList.add("ochiq"); // CSS transition: transform
});
}
panel.addEventListener("transitionend", () => {
panel.style.willChange = ""; // tugadi — qatlam xotirasi bo'shaydi
});Har bir qatlam videokarta xotirasini egallaydi. will-change ni doimiy qoldirish — xotirani behuda band qilish. Qoida: harakatdan oldin qo'ying, harakatdan keyin olib tashlang.
5.6 Layout maydonini kichraytirish
Thrashing'ni to'liq yo'q qilib bo'lmasa, har layout'ni arzonlashtirish mumkin. Rendering pipeline darsidagi contain: layout va content-visibility: auto brauzerga "bu blok ichidagi o'zgarish tashqariga ta'sir qilmaydi" yoki "ko'rinmaydigan qismni hisoblama" deydi. Shunda bitta karta o'zgarganda butun sahifa emas, kichik qism qayta hisoblanadi. Bu davolash emas, yengillashtirish — avval baribir o'qish va yozishni ajrating.
Tekshirib ko'ring: «Bahor» sahifasida har
scrollhodisasida kod sarlavhaningoffsetHeightini o'qiydi, keyin menyu paneligastyle.topyozadi. Sarlavha balandligi sahifa davomida o'zgarmaydi. Qaysi ikki usul bilan bu kodni yengillashtirasiz?
Javob
Birinchisi — keshlash: sarlavha balandligi o'zgarmaydi, uni sahifa ochilganda bir marta o'qib, o'zgaruvchiga saqlang. Shunda scroll paytida o'qish umuman qolmaydi. Ikkinchisi — yozishni rAF'ga qo'yish: bitta kadrda bir nechta scroll hodisasi kelsa ham, style.top kadr boshida bir marta yoziladi. Agar maqsad "sarlavha ko'rinmay qoldimi?" bo'lsa, IntersectionObserver o'lcham o'qishni butunlay olib tashlaydi.
6. Ko'p uchraydigan xatolar
6.1 Sikl ichida o'lcham o'qish
Eng klassik xato. Belgisi: kod to'g'ri ishlaydi, lekin element ko'paygan sari sahifa chiziqli emas, keskin sekinlashadi. Tuzatish: o'qishlarni sikldan oldin bitta massivga yig'ing.
6.2 Yashirin o'qishlar
innerText, focus(), scrollIntoView(), window.scrollY — "o'lcham" degan so'zi yo'q, lekin layout so'raydi. Matn kerak bo'lsa, innerText o'rniga textContent (Kontentni o'zgartirish darsidagi va'da: innerText nega sekinroq — mana sababi). Tuzatish: jadvaldagi ro'yxatni yodda tuting, shubha bo'lsa — Performance panelida tekshiring.
6.3 Funksiya ichida yashiringan o'qish
// ❌ sikl o'zi toza ko'rinadi …
for (const card of cards) {
card.style.width = "50%";
updateBadge(card); // … lekin ichida offsetWidth o'qiladi
}Sikl ichidagi har funksiya chaqiruvi o'qish qilishi mumkin. Bu ko'pincha boshqa dasturchi yozgan yoki kutubxona funksiyasi bo'ladi. Tuzatish: Performance panelidagi Forced reflow havolasi aynan shu funksiyani ko'rsatadi.
6.4 Bir martalik o'lchovga ishonish
Bitta performance.now() o'lchovi — tasodif. Kompyuter boshqa ish bilan band bo'lishi, birinchi ishga tushirish sekinroq bo'lishi mumkin. Tuzatish: 5+ marta, mediana. Va albatta sekinlashtirilgan protsessor bilan ham — foydalanuvchilaringizning ko'pi siznikidan kuchsizroq telefonda.
6.5 Har narsani transform ga aylantirish
transform bilan surilgan element layout'da eski joyida qoladi — qo'shnilari surilmaydi. Ro'yxatdagi element balandligi haqiqatan o'zgarishi kerak bo'lsa (masalan, akkordeon ochilishi), transform: scaleY() matnni cho'zib yuboradi. Tuzatish: layout o'zgarishi kerak bo'lgan joyda layout o'zgaradi — faqat uni sikl ichida o'qish bilan aralashtirmang.
7. Mashqlar
1-mashq (oson): Thrashing qaysi birida?
Har kod uchun ayting: sikl ichida majburiy layout bormi? Nega?
// A
for (const card of cards) {
card.classList.add("narx-yangi"); // faqat color o'zgaradi
total += card.textContent.length;
}
// B
for (const card of cards) {
card.style.padding = "12px";
total += card.getBoundingClientRect().height;
}
// C
const rects = cards.map((card) => card.getBoundingClientRect());
cards.forEach((card, i) => {
card.style.transform = `translateY(${rects[i].height}px)`;
});Yechim
- A — yo'q.
textContentlayout ham, style ham so'ramaydi. Rang klassi yozishlari skript oxirida bir marta hisoblanadi. - B — ha, 100% thrashing.
paddingo'lchamni o'zgartiradi (layout iflos),getBoundingClientRect()uni darhol so'raydi. Har aylanishda to'liq layout. - C — yo'q. Avval hamma o'qishlar (toza layout'dan), keyin hamma yozishlar. Yana bonus: yozilgan
transformlayout'ni umuman iflos qilmaydi.
2-mashq (o'rta): Kartalarni tenglang
Quyidagi funksiya to'g'ri ishlaydi, lekin thrashing qiladi. Uni guruhlangan usulda qayta yozing va oynada ikkalasini solishtiring: har biri tugagach konsolga eng katta balandlik va kartalarning minHeight lari bir xilmi — chiqsin.
function equalizeSlow(cards) {
let max = 0;
for (const card of cards) {
card.style.minHeight = ""; // eski qiymatni tozalash
max = Math.max(max, card.offsetHeight);
for (const other of cards) other.style.minHeight = `${max}px`;
}
}Ishora: uch bosqich — hamma minHeight ni tozalash (yozish), hamma balandlikni o'qish, hammasiga yangi minHeight (yozish). Tozalashdan keyingi o'qishlar bitta majburiy layout qiladi — bu normal.
Yechim
<style>
body { font: 0.95rem/1.4 system-ui, sans-serif; margin: 1rem; }
.row { display: flex; gap: 0.5rem; align-items: flex-start; }
.card { flex: 1; border: 1px solid #c8d6c8; padding: 0.5rem; }
</style>
<div class="row">
<div class="card">Osh</div>
<div class="card">Manti: qovoqli, bug'da pishgan, qatiq bilan</div>
<div class="card">Lag'mon: qo'lda cho'zilgan xamir</div>
</div>
<script>
function equalizeCards(cards) {
// 1) yozish: eski qiymatni tozalash
for (const card of cards) card.style.minHeight = "";
// 2) o'qish: bitta majburiy layout
const heights = cards.map((card) => card.offsetHeight);
const max = Math.max(...heights);
// 3) yozish: hammasiga bir xil balandlik
for (const card of cards) card.style.minHeight = `${max}px`;
return max;
}
const cards = [...document.querySelectorAll(".card")];
const max = equalizeCards(cards);
const target = `${max}px`;
const same = cards.every((card) => card.style.minHeight === target);
console.log("Hammasi bir xil:", same);
console.log("Eng baland karta:", max > 0 ? "topildi" : "yo'q");
</script>Konsolda:
Hammasi bir xil: true
Eng baland karta: topildiAniq piksel sonini chiqarmadik: u shriftga va oyna kengligiga bog'liq. Sekin variantda ichki sikl tufayli har o'qishdan oldin hamma kartaga yozilardi — 3 ta kartada 3 ta majburiy layout, 300 tasida 300 ta. Yangi variantda kartalar soniga qaramay bitta: tozalashdan keyingi birinchi o'qishda. Qolgan o'qishlar toza layout'dan oladi.
3-mashq (qiyin): Mini FastDOM
measure(fn) va mutate(fn) funksiyalarini yozing. Ular ishni darhol bajarmaydi — navbatga qo'yadi. Keyingi kadr boshida (rAF) avval hamma measure lar, keyin hamma mutate lar bajarilsin. Bitta kadrda rAF faqat bir marta so'ralsin.
Sinov: uch karta uchun aralash tartibda chaqiring — measure, mutate, measure, mutate, … Har funksiya konsolga nima qilayotganini yozsin. Natijada konsolda avval hamma "o'qish", keyin hamma "yozish" chiqishi kerak.
Ishora: ikki massiv (reads, writes), bitta scheduled bayrog'i. flush ichida massivlarni bo'shatishni unutmang — aks holda keyingi kadrda eski ishlar qayta bajariladi.
Yechim
<div class="card">Osh</div>
<div class="card">Manti</div>
<div class="card">Somsa</div>
<script>
const reads = [];
const writes = [];
let scheduled = false;
function schedule() {
if (scheduled) return;
scheduled = true;
requestAnimationFrame(flush);
}
function flush() {
scheduled = false;
const nowReads = reads.splice(0); // navbatni olib, bo'shatamiz
const nowWrites = writes.splice(0);
nowReads.forEach((fn) => fn());
nowWrites.forEach((fn) => fn());
}
function measure(fn) { reads.push(fn); schedule(); }
function mutate(fn) { writes.push(fn); schedule(); }
for (const card of document.querySelectorAll(".card")) {
let height = 0;
measure(() => {
height = card.offsetHeight;
console.log(`o'qish: ${card.textContent}`);
});
mutate(() => {
card.style.minHeight = `${height + 10}px`;
console.log(`yozish: ${card.textContent}`);
});
}
</script>Konsolda:
o'qish: Osh
o'qish: Manti
o'qish: Somsa
yozish: Osh
yozish: Manti
yozish: SomsaKod aralash tartibda chaqirdi, lekin bajarilish guruhlangan — shuning uchun majburiy layout yo'q. splice(0) massivning hamma elementlarini olib, uni bo'shatadi: flush paytida yangi measure qo'shilsa, u keyingi kadrga qoladi. Haqiqiy FastDOM shu g'oyaga xatolarni ushlash va bekor qilishni qo'shadi.
4-mashq: Amaliy tajriba — Performance panelida Forced reflow
kurs/mashqlar/13/27-thrashing/index.html ga «O'zingiz sinab ko'ring» bo'limidagi oynaning kodini ko'chiring, kartalar sonini 1 000 ga oshiring. Live Server bilan oching.
- DevTools → Performance → tishli g'ildirak → CPU: 4x slowdown.
- Yozishni boshlang, «Aralash» ni bosing, to'xtating. Main qatoridagi vazifani toping va uni tanlang.
- Pastdagi panelda Forced reflow ni toping va havolani bosing — u qaysi qatorga olib bordi?
- Xuddi shuni «Guruhlangan» uchun qiling. Layout bloklari nechta?
Yechim
1–3. Vazifa juda uzun (sekinlashtirilgan protsessorda bir soniyadan oshishi mumkin), ichida yuzlab siyohrang Layout bloklari. Forced reflow havolasi mixed funksiyasidagi card.dataset.height = card.offsetHeight; qatoriga olib boradi — biz izohda "majburiy layout" deb belgilagan qator.
- «Guruhlangan» — qisqa vazifa, ichida majburiy Layout yo'q. Bitta Layout bloki vazifadan keyin, kadr chizishda keladi.
Xulosani kurs/mashqlar/13/27-thrashing/XULOSA.md ga ikki qatorda yozing: qaysi qator va qanday tuzatildi.
git add 13/27-thrashing
git commit -m "13/27: layout thrashing'ni Performance panelida topish"8. Real ishda
- Scroll va resize ishlovchilari. Har
scrolldagetBoundingClientRect()o'qib,styleyozish — real saytlardagi qotishlarning eng ko'p uchraydigan sababi. Kuzatuvchilar (IntersectionObserver,ResizeObserver) va rAF bilan almashtiriladi. - Kutubxonalar. FastDOM g'oyasi ko'p animatsiya va virtual ro'yxat kutubxonalari ichida bor. React ham DOM'ga yozishlarni yig'ib, bir marta bajaradi — lekin uning ichidagi o'zingiz yozgan o'lchash kodi baribir thrashing qilishi mumkin (React — 17-qismda).
- Audit. Performance panelidagi "Forced reflow" ogohlantirishi — performans auditida birinchi qaraladigan joylardan biri. Uning foydalanuvchi his qiladigan javob tezligiga (INP) ta'sirini keyingi darsda o'lchaymiz.
- Intervyu. "Reflow va repaint farqi?", "Layout thrashing nima, qanday oldini olasiz?", "Animatsiya uchun nega
transform?", "offsetHeighto'qish nega sekin bo'lishi mumkin?" — frontend intervyularining doimiy savollari.
Xulosa
- Konveyer: JavaScript → Style → Layout → Paint → Composite. Layout (reflow) — eng qimmat bosqich;
transform/opacityuni chetlab o'tadi. - Brauzer dangasa: yozishlarni "iflos" deb belgilaydi va kadr oldidan bir marta hisoblaydi.
- Layout iflos paytida o'lcham o'qish (
offsetHeight,getBoundingClientRect(),innerText,scrollY,focus()) — majburiy reflow. Sikl ichida navbatma-navbat — layout thrashing. - O'lchovimizda 500 ta karta: aralash — taxminan 100 ms va 500 ta layout, guruhlangan — 1 ms dan kam va 0 ta majburiy layout.
- Davo: avval o'qish, keyin yozish; yozishni rAF'ga; o'lchamni kuzatuvchilardan olish va keshlash; harakatga
transform;will-changeni vaqtincha.
Keyingi dars: Performance API va Core Web Vitals — performance.mark/measure, PerformanceObserver va foydalanuvchi his qiladigan tezlik: LCP, INP, CLS.
Manbalar
- web.dev: "Avoid large, complex layouts and layout thrashing", "Rendering performance" — web.dev
- Paul Irish: "What forces layout / reflow" — gist.github.com/paulirish
- Chrome DevTools hujjati: "Performance features reference" (Forced reflow) — developer.chrome.com
- HTML Living Standard: "Update the rendering" — html.spec.whatwg.org
- FastDOM — github.com/wilsonpage/fastdom
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!