Mundarija (32)
- Bu darsda
- 1. Nega bu kerak?
- 2. DOM o'zgarganda brauzer nima qiladi
- 3. Bittalab qo'shish va uning muammosi
- 4. DocumentFragment — sahifadan tashqaridagi quti
- 4.1 Fragment nima
- 4.2 Muqobil: massiv va append(...)
- 5. <template> — HTML qolipidan nusxa
- 5.1 Va'dani bajaramiz
- 5.2 Nega qolip, createElement emas?
- 6. replaceChildren — hammasini almashtirish
- 7. O'lchaymiz: qaysi usul tez?
- 7.1 performance.now — sekundomer
- 7.2 Natija: bizning kompyuterda
- 8. Layout thrashing: o'qish va yozishni aralashtirmang
- 8.1 Majburiy layout
- 8.2 Yechim: guruhlash (batching)
- 8.3 DevTools'da ko'rish
- 9. Ko'p uchraydigan xatolar
- 9.1 Nusxani qo'shgandan keyin to'ldirish
- 9.2 cloneNode ga true ni unutish
- 9.3 Qayta chizishda tozalamaslik
- 9.4 Sikl ichida innerHTML +=
- 9.5 Sikl ichida o'lcham o'qish
- 10. Mashqlar
- 1-mashq (oson): Ichimliklar fragmenti
- 2-mashq (o'rta): Taom kartalari qolipdan
- 3-mashq (qiyin): Thrashing'ni tuzating
- 4-mashq: Portfolio qadami — teglar paneli
- 11. Real ishda
- Xulosa
- Manbalar
Ko'p elementni samarali chizish: DocumentFragment, <template> va replaceChildren
Qisqacha: Ko'p elementni avval sahifadan tashqarida —
DocumentFragment"qutisi"da yig'ib, keyin bittaappendbilan qo'shing. Takrorlanadigan HTML tuzilmani<template>da yozib,content.cloneNode(true)bilan nusxalang, ro'yxatni butunlay almashtirishda esareplaceChildrenishlating. Eng katta sekinlik qo'shishdan emas, sikl ichida o'lcham o'qishdan keladi: avval hamma narsani yozing, keyin bir marta o'qing.
Bu darsda
- Brauzer DOM o'zgarganda qachon va qancha ish qilishini tushuntira olasiz.
DocumentFragmentvaappend(...massiv)bilan ko'p elementni bir martada qo'sha olasiz.<template>qolipidan nusxa olib, uni ma'lumot bilan to'ldira olasiz.replaceChildrenbilan ro'yxatni tozalab, qaytadan chiza olasiz.performance.nowbilan kod tezligini o'lchab, layout thrashing'ni topa olasiz.
Oldin bilishingiz kerak: DOM bo'ylab harakatlanish, Element yaratish, joylash va o'chirish, Kontentni o'zgartirish va XSS xavfi, template va slot, Rendering pipeline va animatsiya samaradorligi.
1. Nega bu kerak?
Element yaratish darsida bitta-ikkita elementni createElement va append bilan qo'shdik. «Bahor» menyusida 4 ta taom bor — bu usul bemalol yetadi.
Endi boshqa vaziyat. Jasur aka yetkazib berish xizmatini ochdi. Admin sahifada bugungi buyurtmalar ro'yxati chiqishi kerak — 1000 ta qator. Har qatorda raqam, taom va narx. Ertaga ro'yxat yangilanadi va hammasini qaytadan chizish kerak bo'ladi.
Bunday joyda uch savol tug'iladi:
- 1000 ta elementni qanday qo'shsak, sahifa qotib qolmaydi?
- Har qatordagi bir xil HTML tuzilmani har safar JavaScript'da qo'lda yig'ish shartmi?
- Eski ro'yxatni qanday tez va xavfsiz tozalaymiz?
Javoblar — DocumentFragment, <template> va replaceChildren. Oxirida esa tezlikni o'z ko'zimiz bilan o'lchaymiz — va asosiy dushman kutilmagan joyda chiqadi.
2. DOM o'zgarganda brauzer nima qiladi
Rendering pipeline darsida brauzer har kadrni konveyerda tayyorlashini ko'rdik: uslub hisobi (style) → joylashtirish (layout) → bo'yash (paint) → yig'ish (composite). JavaScript DOM'ga element qo'shsa, sahifa o'zgaradi — demak konveyer qayta ishlashi kerak.
Lekin brauzer har append dan keyin darhol layout qilmaydi. U dangasa, lekin aqlli: o'zgarishlarni yozib boradi, skript tugaguncha kutadi va keyingi kadrda hammasini bir marta hisoblaydi.
Hayotdan o'xshatish: oshpaz Rustam aka. Otabek buyurtmalarni birma-bir olib keladi: "osh", "manti", "yana osh". Rustam aka har buyurtma uchun alohida qozon osmaydi — ro'yxatni yig'adi va bir martada pishiradi.
flowchart LR
JS["Skript: 1000 ta append"] --> K["Skript tugadi"]
K --> S["Style"]
S --> L["Layout — bir marta"]
L --> P["Paint"]
P --> E["Ekranda"]Demak, 1000 ta append — o'z-o'zidan 1000 ta layout emas. Bu darsning muhim xulosasi shundan chiqadi, uni o'lchovda ko'ramiz. Hozircha ko'p elementni qo'shishning uch usulini o'rganamiz.
3. Bittalab qo'shish va uning muammosi
Eng oddiy yo'l — Element yaratish darsidagidek, sikl ichida yaratib, darhol qo'shish:
<ul id="menyu"></ul>
<script>
const taomlar = ["Osh", "Manti", "Lag'mon", "Ko'k choy"];
const menyu = document.getElementById("menyu");
for (const nom of taomlar) {
const li = document.createElement("li");
li.textContent = nom;
menyu.append(li);
}
console.log(menyu.children.length);
</script>Konsolda:
4Ishlaydi. Har aylanishda sahifadagi ul ga yangi li qo'shildi — ya'ni DOM to'rt marta o'zgardi. To'rtta uchun bu muammo emas. Muammo boshqa joyda paydo bo'ladi:
- Sahifa bilan "gaplashish" ko'payadi. Har
appendjonli DOM'ga tegadi. Sikl ichida kimdir elementning o'lchamini o'qisa, brauzer har safar layout qilishga majbur bo'ladi — buni «O'lchaymiz» bo'limida ko'rasiz. - Kod aralashadi. "Yaratish" va "sahifaga qo'yish" bir joyda. Ro'yxatni avval to'liq tayyorlab, keyin bir harakat bilan ko'rsatish o'qishga ham, o'zgartirishga ham qulayroq.
4. DocumentFragment — sahifadan tashqaridagi quti
4.1 Fragment nima
Hujjat bo'lagi (DocumentFragment) — hech qayerga ulanmagan, ko'rinmas "quti" tugun. Unga elementlarni xohlagancha qo'yasiz, sahifa esa hech narsani sezmaydi. Keyin qutini sahifaga append qilganda, uning ichidagilar ko'chadi, qutining o'zi esa bo'sh qoladi.
Hayotdan o'xshatish: patnis. Otabek besh piyola choyni stolga birma-bir tashimaydi — avval oshxonada patnisga teradi, keyin bitta yurishda olib boradi. Stolda piyolalar qoladi, patnis esa bo'shab, oshxonaga qaytadi.
<ul id="menyu"></ul>
<script>
const taomlar = ["Osh", "Manti", "Lag'mon", "Ko'k choy"];
const menyu = document.getElementById("menyu");
const quti = document.createDocumentFragment();
for (const nom of taomlar) {
const li = document.createElement("li");
li.textContent = nom;
quti.append(li);
}
console.log(quti.childNodes.length, menyu.children.length);
console.log(quti.nodeType, quti.nodeName);
menyu.append(quti);
console.log(quti.childNodes.length, menyu.children.length);
</script>Konsolda:
4 0
11 #document-fragment
0 4Qatorma-qator:
- Sikldan keyin qutida 4 ta
li, sahifadagi menyuda — 0. Hamma ish sahifadan tashqarida bo'ldi. nodeType— 11. DOM daraxti darsida va'da qilingan "vaqtinchalik quti" shu.menyu.append(quti)dan keyin qutida 0, menyuda 4. Elementlar ko'chdi, quti bo'shadi.
Fragmentni ikki usulda yaratish mumkin: document.createDocumentFragment() yoki new DocumentFragment(). Natija bir xil. new bilan obyekt yaratishni klasslar darsida chuqur ko'ramiz, hozir birinchi usul yetadi.
Tekshirib ko'ring: Bitta fragmentni ketma-ket ikki xil ro'yxatga
appendqilsangiz, ikkinchi ro'yxatda nechta element bo'ladi?
Javob
Nolta. Birinchi append fragment ichidagi hamma elementni ko'chirib oldi, fragment bo'shab qoldi. Ikkinchi append bo'sh qutini qo'shdi. Element bir vaqtda faqat bitta joyda tura oladi (Element yaratish darsidagi ko'chirish qoidasi).
4.2 Muqobil: massiv va append(...)
append bir nechta argument qabul qiladi (Element yaratish). Demak, elementlarni oddiy massivga yig'ib, spread bilan bir martada berish ham mumkin:
<ul id="menyu"></ul>
<script>
const taomlar = ["Osh", "Manti", "Lag'mon"];
const menyu = document.getElementById("menyu");
const qatorlar = [];
for (const nom of taomlar) {
const li = document.createElement("li");
li.textContent = nom;
qatorlar.push(li);
}
menyu.append(...qatorlar);
console.log(menyu.children);
</script>Konsolda:
HTMLCollection(3) [li, li, li]Natija bir xil — uchta element bitta chaqiruvda qo'shildi. Qaysi birini tanlash — did masalasi. Massiv qulay, chunki uni keyin yana ishlatish mumkin (masalan, nechta qator yaratilganini sanash). Fragment esa <template> bilan ishlaganda o'z-o'zidan chiqadi — keyingi bo'limda ko'rasiz.
Bitta cheklov: juda katta massivni spread bilan berib bo'lmaydi — har element alohida argumentga aylanadi, argumentlar soni esa cheklangan. Chrome'da 500 000 ta element bilan sinadik: Uncaught RangeError: Maximum call stack size exceeded ("chaqiruvlar stekining eng katta hajmidan oshib ketdi"). Bunday hajmda fragment xavfsizroq.
5. <template> — HTML qolipidan nusxa
5.1 Va'dani bajaramiz
template va slot darsida «Bahor» menyu jadvali uchun qolip yozgan edik va JavaScript qismini shu darsga qoldirgan edik. Mana o'sha qolip, endi ishlaydigan kod bilan:
<table>
<caption>Menyu</caption>
<thead>
<tr>
<th scope="col">Taom</th>
<th scope="col">Narxi (so'm)</th>
</tr>
</thead>
<tbody id="menyu-qatorlar"></tbody>
</table>
<template id="taom-qolip">
<tr>
<td class="nomi"></td>
<td class="narxi"></td>
</tr>
</template>
<script>
const taomlar = [
{ nom: "Osh", narx: 35000 },
{ nom: "Manti", narx: 30000 },
{ nom: "Lag'mon", narx: 28000 },
{ nom: "Ko'k choy", narx: 5000 },
];
function somYoz(son) {
const raqamlar = String(son);
let natija = "";
for (let i = 0; i < raqamlar.length; i++) {
const qolgani = raqamlar.length - i;
if (i > 0 && qolgani % 3 === 0) {
natija += " ";
}
natija += raqamlar[i];
}
return natija;
}
const qolip = document.getElementById("taom-qolip");
const quti = document.createDocumentFragment();
for (const taom of taomlar) {
const nusxa = qolip.content.cloneNode(true);
nusxa.querySelector(".nomi").textContent = taom.nom;
nusxa.querySelector(".narxi").textContent = somYoz(taom.narx);
quti.append(nusxa);
}
document.getElementById("menyu-qatorlar").append(quti);
console.log(document.querySelectorAll("tbody tr").length);
console.log(document.querySelector(".narxi").textContent);
</script>Konsolda:
4
35 000Natija tabida to'rt qatorli jadval chiqdi. Kodni qatorma-qator o'qiymiz:
qolip.content— qolip ichidagi tuzilma. U allaqachonDocumentFragment: brauzertemplateichini aynan shunday qutiga joylaydi.cloneNode(true)— qutining to'liq nusxasi, ichidagi hamma avlodlari bilan. Qolipning o'zi o'zgarmaydi — keyingi taom uchun ham kerak.nusxa.querySelector(".nomi")— nusxa ichidan katakni topamiz. Fragmentda hamquerySelectorbor.textContent— matnni xavfsiz qo'yish (Kontentni o'zgartirish va XSS).somYoz— sonni35 000ko'rinishida yozadigan funksiya, 08-qism yakuniy loyihasidan tanish.quti.append(nusxa)— tayyor qatorni umumiy qutiga. Oxirida hamma qatorlar bittaappendbilan jadvalga tushadi.
Diqqat: Pul va aniq hisob-kitob darsida
Intl.NumberFormat("uz-UZ")ni ko'rgandingiz. Node'da u35 000beradi, lekin biz sinagan Chrome 154 da —35,000, vergul bilan. Brauzer o'zbek tilining son formatini Node'dagidek to'liq bilmaydi. Shuning uchun brauzer kodida hamma joyda bir xil chiqadigan o'zsomYozfunksiyamizdan foydalanamiz.
5.2 Nega qolip, createElement emas?
Bitta li uchun createElement qulay. Lekin bitta karta — sarlavha, narx, rasm, tugma, ichma-ich bloklar — JavaScript'da qo'lda yig'ilsa, 20–30 qator kod bo'ladi. HTML tuzilmani esa kodni o'qib tasavvur qilish qiyin.
Qolip bu ishni ikkiga bo'ladi:
| Kim | Nima qiladi |
|---|---|
HTML (template) |
Tuzilma: qaysi teglar, qaysi tartibda, qaysi class'lar |
| JavaScript | Ma'lumot: qaysi katakka qaysi matn |
Dizayner Nodira "narx ustuniga ikonka qo'shaylik" desa, faqat qolipni o'zgartirasiz — JavaScript'ga tegmaysiz. template darsida aytilgan qoida ham shu yerda ishlaydi: qolip ichida id emas, class — chunki qolip 40 marta nusxalanadi.
Tekshirib ko'ring: Kodda
cloneNode(true)o'rnigaqolip.contentning o'zini to'ldirib,quti.append(qolip.content)qilsak nima bo'ladi?
Javob
Birinchi aylanishda qolipdagi asl tr jadvalga ko'chib ketadi va qolip bo'shab qoladi. Ikkinchi aylanishda qolip.content.querySelector(".nomi") — null, textContent ga yozishda Cannot set properties of null xatosi chiqadi. Qolipdan doim nusxa oling.
6. replaceChildren — hammasini almashtirish
Ro'yxat yangilanganda eski qatorlarni olib tashlab, yangilarini qo'yish kerak. Buning uchun bitta metod bor — replaceChildren. U elementning barcha bolalarini o'chiradi va o'rniga berilganlarni qo'yadi:
<ul id="menyu">
<li>Osh</li>
<li>Manti</li>
</ul>
<script>
const menyu = document.getElementById("menyu");
console.log(menyu.children.length);
const yangi1 = document.createElement("li");
yangi1.textContent = "Lag'mon";
const yangi2 = document.createElement("li");
yangi2.textContent = "Ko'k choy";
menyu.replaceChildren(yangi1, yangi2);
console.log(menyu.children.length, menyu.firstElementChild);
menyu.replaceChildren();
console.log(menyu.children.length, menyu.childNodes.length);
</script>Konsolda:
2
2 <li>Lag'mon</li>
0 0- Argument bilan — eskilarini o'chirib, yangilarini qo'ydi. Fragment ham,
...massivham berish mumkin:menyu.replaceChildren(quti). - Argumentsiz — shunchaki tozaladi. E'tibor bering,
childNodesham 0: bo'sh joy matn tugunlari ham ketdi.
Eski kodda tozalash uchun menyu.innerHTML = "" ni ko'p uchratasiz. U ham ishlaydi, lekin innerHTML — HTML tahlilchisini (parser) ishga tushiradigan "og'ir" asbob (Kontentni o'zgartirish va XSS). replaceChildren() esa niyatni aniq aytadi: "bolalarni almashtir". Baseline ma'lumotlariga ko'ra, u hamma asosiy brauzerlarda 2020-yildan beri bor.
Tozalash va chizishni birlashtirsak, qayta chizishning to'liq qolipi chiqadi:
function menyuniChiz(taomlar) {
const quti = document.createDocumentFragment();
for (const taom of taomlar) {
// qolipdan nusxa, to'ldirish, quti.append(nusxa)
}
jadval.replaceChildren(quti); // eskisi ketdi, yangisi keldi
}Bu funksiyani necha marta chaqirsangiz ham, jadvalda faqat oxirgi ma'lumot turadi. Keyingi darsda aynan shu g'oyadan "holatdan UI chizish" qolipi o'sib chiqadi.
Tekshirib ko'ring:
menyuniChizichidajadval.replaceChildren(quti)o'rnigajadval.append(quti)yozildi. Funksiya uch marta chaqirilsa, jadvalda nechta qator bo'ladi (har safar 4 ta taom)?
Javob
12 ta. append eskisini o'chirmaydi — har chaqiruv yana 4 ta qatorni oxiriga qo'shadi. Jadvalda har taom uch marta takrorlanadi. «Ko'p uchraydigan xatolar» bo'limida bu tuzoq yana uchraydi.
7. O'lchaymiz: qaysi usul tez?
7.1 performance.now — sekundomer
"Tez" yoki "sekin" degan gapni taxmin bilan emas, o'lchab aytamiz. performance.now() — sahifa ochilgandan beri o'tgan vaqt, millisekundlarda, kasr bilan. Ish oldidan va keyin olib, ayirmasini hisoblasak — ish qancha davom etganini bilamiz. Bu sekundomerning "boshla" va "to'xtat" tugmalari.
Quyidagi oyna 1000 ta qatorni uch usulda qo'shadi va har biriga ketgan vaqtni konsolga yozadi:
<style>
#royxat { height: 120px; overflow: auto; }
</style>
<ul id="royxat"></ul>
<script>
const royxat = document.getElementById("royxat");
const N = 1000;
function qatorYarat(raqam) {
const li = document.createElement("li");
li.textContent = `Buyurtma №${raqam}`;
return li;
}
function olcha(nom, ish) {
royxat.replaceChildren();
const boshi = performance.now();
ish();
royxat.offsetHeight; // hozir joylashtirishga majburlaymiz
const vaqt = performance.now() - boshi;
console.log(`${nom}: ${vaqt.toFixed(1)} ms`);
}
olcha("Bittalab", () => {
for (let i = 1; i <= N; i++) {
royxat.append(qatorYarat(i));
}
});
olcha("Fragment", () => {
const quti = document.createDocumentFragment();
for (let i = 1; i <= N; i++) {
quti.append(qatorYarat(i));
}
royxat.append(quti);
});
olcha("Har qadamda o'lchab", () => {
for (let i = 1; i <= N; i++) {
royxat.append(qatorYarat(i));
royxat.offsetHeight;
}
});
</script>Yangi narsa bitta: royxat.offsetHeight — elementning balandligi, pikselda. O'lchamlarni keyingi darsda to'liq o'rganamiz. Hozir bitta fakt muhim: uni o'qish uchun brauzer shu zahoti layout qilishi kerak — aks holda balandlikni bilmaydi.
Konsolga qarang. Raqamlar sizda boshqacha chiqadi: ular kompyuter, telefon va brauzerga bog'liq, har ochilishda ham biroz o'zgaradi. Muhimi — raqamlarning bir-biriga nisbati. "Tahrirlab ko'rish" bilan N ni 2000 ga oshirib ham ko'ring.
7.2 Natija: bizning kompyuterda
Xuddi shu kodni Chrome 154 da (Windows, ekransiz rejim) to'qqiz martadan ishga tushirib, o'rtacha (median) qiymatni oldik. To'rtinchi ustun — innerHTML += bilan qo'shish, uni quyida alohida ko'ramiz:
- Bittalab17,3 ms
- Fragment15,5 ms
- Har qadamda o'lchab1000 marta layout201,7 ms
- innerHTML +=har safar qayta tahlil472,2 ms
Manba: Chrome 154, Windows, ekransiz rejim; 9 o'lchovning medianasi, 2026-10-04. Raqamlar kompyuterga bog'liq — nisbatiga qarang
Grafikdan uch xulosa:
- Fragment bittalabdan atigi ~10% tez. Zamonaviy brauzer o'zgarishlarni o'zi yig'ib, bir marta layout qiladi — «DOM o'zgarganda brauzer nima qiladi» bo'limidagi Rustam aka kabi. Fragmentning asosiy foydasi — toza kod va pastdagi xavfdan himoya.
- Sikl ichida o'lcham o'qish — 12 baravar sekin. Har o'qish brauzerni "yig'ib qo'yish"dan voz kechib, shu zahoti layout qilishga majbur qildi. 1000 o'qish — 1000 layout.
innerHTML +=— eng sekini. Har aylanishda butun ro'yxat satrga aylanadi va qaytadan tahlil qilinadi: 1-qadamda 1 qator, 1000-qadamda 1000 qator.
Shuning uchun tez-tez eshitiladigan "fragment ishlatmasangiz, sahifa sekinlashadi" degan gap bugun to'liq to'g'ri emas. To'g'risi: o'qish va yozishni aralashtirsangiz, sahifa sekinlashadi.
Bu o'lchovning chegarasini ham biling. U bitta kompyuterda, bitta brauzerda, bo'sh sahifada qilingan. Ko'p uslubli, murakkab sahifada layout qimmatroq — demak thrashing yanada og'irroq bo'ladi. Arzon telefonda esa hamma ustunlar bir necha baravar uzunroq. Shuning uchun haqiqiy loyihada sekinlikni o'z sahifangizda, imkon bo'lsa, oddiy telefonda o'lchang (Rendering pipeline darsida Performance panelida CPU'ni 4 baravar sekinlashtirgan edik — shu usul bu yerda ham ishlaydi).
Tekshirib ko'ring: Ro'yxatda 10 ta qator bor. Thrashing'li kodni tuzatish shartmi?
Javob
Sezilarli foyda bermaydi: 10 ta layout — millisekundning bir qismi. Lekin odatni hozirdan to'g'ri qilgan ma'qul. Ro'yxatlar o'sadi: bugun 10 ta bo'lgan buyurtma ertaga 1000 taga yetadi, sekinlik esa birdaniga paydo bo'ladi va uning sababini topish qiyinroq.
8. Layout thrashing: o'qish va yozishni aralashtirmang
8.1 Majburiy layout
Sikl ichida yozish (append) va o'qish (offsetHeight) navbatma-navbat kelsa, brauzer har safar "dangasalik"dan voz kechadi. Bu holatning nomi — layout thrashing ("layout'ni titratish"). Rendering pipeline darsida u Performance panelida qanday ko'rinishini aytgan edik — mana uning kodi.
sequenceDiagram
participant JS as Skript
participant B as Brauzer
JS->>B: yoz: qator qo'sh
JS->>B: o'qi: balandlik?
B->>B: layout (majburiy)
B-->>JS: javob
JS->>B: yoz: qator qo'sh
JS->>B: o'qi: balandlik?
B->>B: yana layout
Note over JS,B: ... 1000 martaQaysi xususiyatlarni o'qish layout'ni majburlaydi? O'lcham va joylashuv bilan bog'liq deyarli hammasi: offsetHeight, offsetWidth, clientHeight, scrollTop, getBoundingClientRect(), getComputedStyle() (classList va stillar darsidagi). Ularni keyingi darsda batafsil ko'rasiz.
8.2 Yechim: guruhlash (batching)
Qoida: avval hamma yozishlar, keyin hamma o'qishlar. Bu usul guruhlash (batching) deyiladi. O'qish natijasi sikl ichida kerak bo'lsa, uni sikldan oldin bir marta o'qib, o'zgaruvchiga saqlang.
Masalan, har qatorga ro'yxatning boshlang'ich balandligini yozmoqchimiz:
// ❌ har aylanishda o'qish — har safar layout
for (const qator of qatorlar) {
qator.textContent += ` (${royxat.offsetHeight}px)`;
royxat.append(qator);
}
// ✅ bir marta o'qib, keyin faqat yozish
const balandlik = royxat.offsetHeight;
for (const qator of qatorlar) {
qator.textContent += ` (${balandlik}px)`;
royxat.append(qator);
}Ikkinchi variantda layout bitta. Yozish va o'qish ko'p joyda tarqalgan murakkab sahifalarda ularni kadrlar bo'yicha ajratadigan maxsus asbob ham bor — requestAnimationFrame. Uni kadrlar bilan ishlash darsida o'rganamiz, hozir bilish shart emas.
Tekshirib ko'ring: Sikl ichida faqat
li.textContentyoziladi vaclassList.addchaqiriladi, hech narsa o'qilmaydi. Bunda layout thrashing bormi?
Javob
Yo'q. Faqat yozish — brauzer hammasini yig'ib, skript tugagach bir marta layout qiladi. Thrashing uchun yozish va o'lcham o'qish navbatma-navbat kelishi kerak. textContent ni o'qish ham layout talab qilmaydi — u matn, o'lcham emas. Ehtiyot bo'ling: uning qarindoshi innerText esa talab qiladi, chunki u faqat ko'rinadigan matnni qaytaradi va buning uchun CSS'ni hisoblashi kerak.
8.3 DevTools'da ko'rish
Thrashing'ni ko'z bilan ko'rish uchun DevTools'ning Performance panelidan foydalaning (Rendering pipeline darsida ochgansiz):
- Yuqoridagi o'lchov kodini o'z faylingizga ko'chiring va Live Server bilan oching.
- Performance panelida yozishni boshlang (aylana tugma), sahifani yangilang (
F5), yozishni to'xtating. - Main qatorida bitta uzun vazifa ichida o'nlab mayda siyohrang Layout bloklari ko'rinadi. Ustida qizil burchak va Forced reflow ogohlantirishi.
Fragment va bittalab variantlarida esa bitta Layout bloki — skript oxirida.
9. Ko'p uchraydigan xatolar
9.1 Nusxani qo'shgandan keyin to'ldirish
<ul id="menyu"></ul>
<template id="taom-qolip">
<li><span class="nomi"></span></li>
</template>
<script>
const qolip = document.getElementById("taom-qolip");
const menyu = document.getElementById("menyu");
const nusxa = qolip.content.cloneNode(true);
menyu.append(nusxa);
nusxa.querySelector(".nomi").textContent = "Osh";
</script>Konsolda:
Uncaught TypeError: Cannot set properties of null (setting 'textContent')Tarjimasi: "null ning xususiyatini o'rnatib bo'lmaydi ('textContent' ni o'rnatayotganda)". nusxa — fragment. append dan keyin uning ichidagi li menyuga ko'chdi, fragment bo'shab qoldi va querySelector hech narsa topmadi. Tuzatish: avval to'ldiring, keyin qo'shing. Yoki qo'shishdan oldin elementni o'zgaruvchiga oling: const li = nusxa.firstElementChild; — li ko'chgandan keyin ham o'sha element bo'lib qoladi.
9.2 cloneNode ga true ni unutish
qolip.content.cloneNode() — argumentsiz faqat qutining o'zini nusxalaydi, ichidagilarsiz. Natijada yana bo'sh fragment va null xatosi. Tuzatish: qolipdan doim cloneNode(true) — "chuqur nusxa".
9.3 Qayta chizishda tozalamaslik
Ro'yxatni yangilash funksiyasi append bilan yozilgan bo'lsa, ikkinchi chaqiruvdan keyin ro'yxatda har taom ikki marta turadi. Tuzatish: qayta chizishda append emas, replaceChildren — u avval tozalaydi.
9.4 Sikl ichida innerHTML +=
// ❌ har aylanishda butun ro'yxat qayta tahlil qilinadi
for (const buyurtma of buyurtmalar) {
royxat.innerHTML += `<li>${buyurtma.nom}</li>`;
}Uch muammo birdan:
- Sekin — grafikdagi eng uzun ustun.
- Avvalgi elementlar yo'q qilinib, qayta yaratiladi. Ularga biriktirilgan hodisa tinglovchilari va maydonlarga yozilgan matn yo'qoladi.
- XSS teshigi —
buyurtma.nomda<img src=x onerror=...>bo'lsa, u kod sifatida ishlaydi (Kontentni o'zgartirish va XSS).
Tuzatish: createElement yoki qolip + textContent + fragment.
9.5 Sikl ichida o'lcham o'qish
«Layout thrashing» bo'limidagi asosiy tuzoq. Belgisi — kod mantiqan to'g'ri, lekin ro'yxat kattalashganda sahifa "qotadi". Tuzatish: o'qishni sikldan tashqariga chiqaring.
10. Mashqlar
1-mashq (oson): Ichimliklar fragmenti
ichimliklar massividagi har ichimlik uchun li yarating, ularni fragmentga yig'ing va #ichimliklar ga bitta append bilan qo'shing. Oxirida konsolga ro'yxatdagi elementlar sonini chiqaring: 3.
<h3>Ichimliklar</h3>
<ul id="ichimliklar"></ul>
<script>
const ichimliklar = ["Ko'k choy", "Qora choy", "Kompot"];
// shu yerga yozing
</script>Yechim
<ul id="ichimliklar"></ul>
<script>
const ichimliklar = ["Ko'k choy", "Qora choy", "Kompot"];
const royxat = document.getElementById("ichimliklar");
const quti = document.createDocumentFragment();
for (const nom of ichimliklar) {
const li = document.createElement("li");
li.textContent = nom;
quti.append(li);
}
royxat.append(quti);
console.log(royxat.children.length);
</script>Konsolda:
32-mashq (o'rta): Taom kartalari qolipdan
Qolip tayyor: article ichida sarlavha (.nomi) va narx (.narxi). Har taom uchun nusxa oling, to'ldiring va #kartalar ga qo'shing. Narx 35 000 so'm ko'rinishida bo'lsin. Oxirida konsolga kartalar ro'yxatini chiqaring.
Ishora: somYoz funksiyasi tayyor turibdi — `${somYoz(taom.narx)} so'm` (Template literals). Hammasini qutiga yig'ib, replaceChildren bilan qo'shing.
<h2>Bugungi taomlar</h2>
<div id="kartalar"></div>
<template id="karta-qolip">
<article class="taom-karta">
<h3 class="nomi"></h3>
<p class="narxi"></p>
</article>
</template>
<script>
const taomlar = [
{ nom: "Osh", narx: 35000 },
{ nom: "Manti", narx: 30000 },
];
function somYoz(son) {
const raqamlar = String(son);
let natija = "";
for (let i = 0; i < raqamlar.length; i++) {
const qolgani = raqamlar.length - i;
if (i > 0 && qolgani % 3 === 0) {
natija += " ";
}
natija += raqamlar[i];
}
return natija;
}
// shu yerga yozing
</script>Yechim
<div id="kartalar"></div>
<template id="karta-qolip">
<article class="taom-karta">
<h3 class="nomi"></h3>
<p class="narxi"></p>
</article>
</template>
<script>
const taomlar = [
{ nom: "Osh", narx: 35000 },
{ nom: "Manti", narx: 30000 },
];
function somYoz(son) {
const raqamlar = String(son);
let natija = "";
for (let i = 0; i < raqamlar.length; i++) {
const qolgani = raqamlar.length - i;
if (i > 0 && qolgani % 3 === 0) {
natija += " ";
}
natija += raqamlar[i];
}
return natija;
}
const qolip = document.getElementById("karta-qolip");
const kartalar = document.getElementById("kartalar");
const quti = document.createDocumentFragment();
for (const taom of taomlar) {
const nusxa = qolip.content.cloneNode(true);
nusxa.querySelector(".nomi").textContent = taom.nom;
nusxa.querySelector(".narxi").textContent =
`${somYoz(taom.narx)} so'm`;
quti.append(nusxa);
}
kartalar.replaceChildren(quti);
console.log(kartalar.children);
</script>Konsolda:
HTMLCollection(2) [article.taom-karta, article.taom-karta]Qolip ichidagi bo'sh joy matn tugunlari ham nusxalanadi — childNodes ularni sanaydi, children esa faqat ikki kartani ko'rsatdi.
3-mashq (qiyin): Thrashing'ni tuzating
Quyidagi kod har qatorga ro'yxatning eni haqida yozadi, lekin sikl ichida o'qiydi. Kodni shunday o'zgartiringki, ro'yxat eni faqat bir marta o'qilsin, natija esa o'zgarmasin. Konsolda chiqadigan oxirgi qator — Qatorlar: 500.
Ishora: royxat.offsetWidth qatorlar qo'shilishi bilan o'zgarmaydi (ro'yxat eni sahifa eniga bog'liq), shuning uchun uni sikldan oldin bitta o'zgaruvchiga olish xavfsiz.
<ul id="royxat"></ul>
<script>
const royxat = document.getElementById("royxat");
const boshi = performance.now();
for (let i = 1; i <= 500; i++) {
const li = document.createElement("li");
const en = royxat.offsetWidth;
li.textContent = `Qator ${i}, ro'yxat eni ${en}px`;
royxat.append(li);
}
const vaqt = performance.now() - boshi;
console.log(`Vaqt: ${vaqt.toFixed(1)} ms`);
console.log(`Qatorlar: ${royxat.children.length}`);
</script>Yechim
<ul id="royxat"></ul>
<script>
const royxat = document.getElementById("royxat");
const boshi = performance.now();
const en = royxat.offsetWidth;
const quti = document.createDocumentFragment();
for (let i = 1; i <= 500; i++) {
const li = document.createElement("li");
li.textContent = `Qator ${i}, ro'yxat eni ${en}px`;
quti.append(li);
}
royxat.append(quti);
const vaqt = performance.now() - boshi;
console.log(`Vaqt: ${vaqt.toFixed(1)} ms`);
console.log(`Qatorlar: ${royxat.children.length}`);
</script>O'qish sikldan tashqariga chiqdi — layout bir marta. Ikkala versiyadagi vaqtni solishtiring. Bizning kompyuterda (Chrome 154) birinchisi taxminan 57 ms, ikkinchisi 1 ms dan kam chiqdi. Adolat uchun: ikkinchi versiyada yagona layout skript tugagandan keyin bo'ladi va o'lchovga kirmaydi — lekin u baribir bitta, 500 ta emas. Fragment bu yerda qo'shimcha xavfsizlik: sikl ichida hech narsa sahifaga tegmaydi, shuning uchun kimdir keyinroq o'qish qo'shsa ham, thrashing bo'lmaydi.
4-mashq: Portfolio qadami — teglar paneli
loyihalar/index.html dagi kartalar HTML'da yozilgan va shunday qoladi: JavaScript o'chiq bo'lsa ham, sahifa to'liq ishlaydi. JavaScript esa ularning ustiga qulaylik qo'shadi. Bu yondashuv progressive enhancement deyiladi (Brauzer mosligi va progressive enhancement darsidan tanish). Ikki darsdan keyin, state → render darsida, loyihalarni texnologiya bo'yicha saralaydigan filtr qilamiz. Bugun uning birinchi qismi — tugmalar paneli:
- Texnologiyalar ro'yxati kartalarda allaqachon bor — Element yaratish darsida qo'shgan
data-texnologiyalar="HTML CSS"atributi, bo'sh joy bilan ajratilgan (Atributlar vadataset). U har kartada bo'lishi shart — o'sha darsdagi qoida: panel va filtr hamma kartadan yig'adi. asosiy.jsgateglarPaneliniChizfunksiyasini yozing. U kartalardan hamma texnologiyalarni takrorlanmas qilib yig'sin:split(" ")(Kesish va bo'lish) vaincludes(Massivda qidirish).- "Hammasi" va har texnologiya uchun
buttonyarating, ularni fragmentga yig'ing. Har tugmagadata-tegyozing: "Hammasi" uchunhammasi, qolganlariga texnologiya nomi. - Fragmentni yangi
pichiga qo'yib, uni.loyihalardan oldin joylang (before— Element yaratish). .loyihalaryo'q sahifalarda funksiya hech narsa qilmasin.
Tugmalar hozircha hech narsa qilmaydi — ularni state → render darsida holatga ulaysiz.
Yechim
loyihalar/index.html da har karta boshlanishi (sizning texnologiyalaringiz bilan; atribut har kartada):
<article data-texnologiyalar="HTML CSS">asosiy.js ga:
function teglarPaneliniChiz() {
const joy = document.querySelector(".loyihalar");
if (joy === null) {
return;
}
const teglar = [];
for (const karta of joy.querySelectorAll("article")) {
for (const teg of karta.dataset.texnologiyalar.split(" ")) {
if (!teglar.includes(teg)) {
teglar.push(teg);
}
}
}
const quti = document.createDocumentFragment();
quti.append(tugmaYarat("Hammasi", "hammasi"));
for (const teg of teglar) {
quti.append(" ", tugmaYarat(teg, teg));
}
const panel = document.createElement("p");
panel.append(quti);
joy.before(panel);
}
function tugmaYarat(matn, teg) {
const tugma = document.createElement("button");
tugma.type = "button";
tugma.textContent = matn;
tugma.dataset.teg = teg;
return tugma;
}
teglarPaneliniChiz();Kartalarga o'xshash parchada tekshiring:
<main>
<h1>Loyihalar</h1>
<div class="loyihalar">
<article data-texnologiyalar="HTML CSS">
<h2>Choyxona «Bahor» sayti</h2>
</article>
<article data-texnologiyalar="HTML">
<h2>Semantik blog maqolasi</h2>
</article>
</div>
</main>
<script>
function teglarPaneliniChiz() {
const joy = document.querySelector(".loyihalar");
if (joy === null) {
return;
}
const teglar = [];
for (const karta of joy.querySelectorAll("article")) {
for (const teg of karta.dataset.texnologiyalar.split(" ")) {
if (!teglar.includes(teg)) {
teglar.push(teg);
}
}
}
const quti = document.createDocumentFragment();
quti.append(tugmaYarat("Hammasi", "hammasi"));
for (const teg of teglar) {
quti.append(" ", tugmaYarat(teg, teg));
}
const panel = document.createElement("p");
panel.append(quti);
joy.before(panel);
}
function tugmaYarat(matn, teg) {
const tugma = document.createElement("button");
tugma.type = "button";
tugma.textContent = matn;
tugma.dataset.teg = teg;
return tugma;
}
teglarPaneliniChiz();
console.log(document.querySelectorAll("[data-teg]"));
console.log(document.querySelector("h1").nextElementSibling);
</script>Konsolda:
NodeList(3) [button, button, button]
<p>…</p>Uch tugma — "Hammasi", "HTML", "CSS". HTML ikki kartada bor, lekin panelda bir marta: includes takrorni to'xtatdi. Panel sarlavhadan keyin, kartalardan oldin turibdi.
Ikki nozik joy:
tugmaYarat— alohida funksiya. Ikki joyda bir xil tugma yasaladi — kodni takrorlamaslik uchun. FunksiyateglarPaneliniChizdan keyin e'lon qilingan, lekin chaqiruv faylning oxirida — u paytda ikkalasi ham tayyor.- Atribut bo'lmasa. Biror kartaga
data-texnologiyalaryozishni unutsangiz,dataset.texnologiyalar—undefinedvasplitdaCannot read properties of undefined (reading 'split')xatosi chiqadi. Bu yaxshi: xato darhol ko'rinadi va qaysi kartani tuzatishni bilasiz.
Commit: JS: loyihalar sahifasiga teglar panelini qo'sh.
11. Real ishda
- Uzun ro'yxatlar — chat tarixi, buyurtmalar jadvali, Telegram kanal lentasi — bittalab qo'shish emas, guruhlab chiziladi. Juda uzun ro'yxatlarda (10 000+) faqat ekranda ko'rinadigan qatorlar chiziladi — bu virtualizatsiya, uni uzun ro'yxatlar darsida ko'rasiz.
- Freymvorklar (React, Vue — kursda alohida o'rganamiz) o'zgarishlarni o'zi guruhlaydi va DOM'ga bir martada yozadi. Ularning "batching" degan imkoniyati — aynan bugungi g'oya.
- Performance auditi. Lighthouse yoki Performance panelida "Forced reflow" ogohlantirishi — real loyihalardagi eng ko'p uchraydigan sekinlik sabablaridan biri. Uni topish va tuzatish — frontend dasturchining kundalik ishi.
- Web Components ham
template+cloneNodeustiga quriladi (Web Components). - Intervyuda: "
DocumentFragmentnima va qachon kerak?", "Layout thrashing nima, qanday oldini olasiz?", "innerHTML +=ning qanday muammolari bor?" — o'rta darajadagi tez-tez beriladigan savollar.
Xulosa
- Brauzer DOM o'zgarishlarini yig'ib, skript tugagach bir marta layout qiladi — agar siz uni majburlamasangiz.
DocumentFragment— sahifadan tashqaridagi quti: unga yig'asiz,appendqilganda ichidagilar ko'chadi, quti bo'shaydi.<template>— HTML tuzilma;content.cloneNode(true)bilan nusxa, avval to'ldiring, keyin qo'shing.replaceChildren(...)— tozalab, yangisini qo'yadi; argumentsiz — faqat tozalaydi.- Eng katta sekinlik — sikl ichida o'lcham o'qish (layout thrashing): avval yozing, keyin bir marta o'qing.
- Tezlik haqidagi gapni o'lchab aytamiz:
performance.now()va Performance paneli.
Keyingi dars: Element o'lchamlari va scroll — bugun "sekin" deb tanigan offsetHeight va uning qarindoshlari: element qancha joy egallaydi, qayerda turadi va sahifani qanday aylantirish mumkin.
Manbalar
- MDN: "DocumentFragment", "Element: replaceChildren()", "HTMLTemplateElement: content property" — developer.mozilla.org
- MDN: "Performance: now() method" — developer.mozilla.org
- web.dev: "Avoid large, complex layouts and layout thrashing" — web.dev
- Paul Irish: "What forces layout / reflow" — gist.github.com/paulirish
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!