Mundarija (33)
- Bu darsda
- 1. Nega bu kerak?
- 2. Debounce: "to'xtagach, bir marta"
- 2.1 Eslab olamiz
- 2.2 Vaqt chizig'ida
- 3. «Bahor» qidiruvi: debounce + AbortController
- 3.1 Jonli misol
- 3.2 Nega AbortController ham kerak?
- 4. cancel va flush
- 4.1 Ikki qo'shimcha tugma
- 4.2 Sahifa yopilganda
- 5. Throttle: "ko'pi bilan har N ms da bir marta"
- 5.1 G'oya
- 5.2 Eng oddiy throttle
- 5.3 Leading va trailing
- 6. Debounce, throttle va rAF — yonma-yon
- 6.1 Jonli taqqoslash
- 6.2 rAF throttle: "har kadrda bir marta"
- 7. Leading debounce: ikki marta bosishdan himoya
- 8. Ko'p uchraydigan xatolar
- 8.1 Debounce'ni har hodisada qayta yaratish
- 8.2 Vizual yangilanish uchun debounce
- 8.3 Trailing'siz throttle bilan yakuniy holat
- 8.4 Juda uzun kutish
- 8.5 Kutayotgan chaqiruvni unutish
- 9. Mashqlar
- 1-mashq (oson): resize — yakuniy o'lcham
- 2-mashq (o'rta): "O'qildi" chizig'i — rAF throttle bilan
- 3-mashq (qiyin): Trailing'li throttle
- 4-mashq: Vazifalar qadami — qoralama debounce bilan
- 10. Real ishda
- Xulosa
- Manbalar
JavaScript debounce va throttle: tez-tez keladigan hodisalarni kamaytirish
Qisqacha: Debounce — "chaqiruvlar to'xtagach, jimlikdan keyin bir marta, oxirgisi bilan bajar" (qidiruv maydoni, oyna o'lchami). Throttle — "chaqiruvlar davom etsa ham, ko'pi bilan har N ms da bir marta bajar" (scroll, sichqoncha harakati). Ikkalasi closure va taymer bilan yoziladi:
const search = debounce(fn, 300). Qo'shimcha sozlamalar: boshida bajarish (leading), oxirida bajarish (trailing), bekor qilish (cancel) va darhol bajarish (flush).
Bu darsda
- Debounce va throttle farqini vaqt chizig'ida tushuntira olasiz va vaziyatga qarab to'g'risini tanlaysiz.
- Ikkalasini o'zingiz yozasiz,
cancelvaflushimkoniyatlari bilan. - Leading va trailing nimani anglatishini bilasiz: "ikki marta bosish" himoyasi va "oxirgi holat yo'qolmasin" talabi.
- Vizual yangilanishlarni
requestAnimationFramebilan "har kadrda bir marta"ga cheklay olasiz.
Oldin bilishingiz kerak: Fokus, scroll va resize hodisalari, Closure amaliyotda, Taymerlar, Bekor qilish: AbortController, requestAnimationFrame.
1. Nega bu kerak?
«Bahor» saytiga qidiruv maydoni qo'shildi. Mehmon "lag'mon" deb yozadi — 7 ta harf. Har harfda serverga so'rov ketsa — 7 ta so'rov. Ulardan 6 tasining javobi hech kimga kerak emas: mehmon hali yozyapti. Telefonda sekin internet bo'lsa, javoblar aralash tartibda keladi va ekranda "l" ning natijasi "lag'mon" nikidan keyin chiqib qolishi mumkin.
Ikkinchi holat. Sahifa tepasida "qancha o'qildi" chizig'i bor — u scroll hodisasida yangilanadi. Telefonda barmoq bilan aylantirganda scroll soniyasiga o'nlab marta keladi. Har birida og'ir hisob qilinsa — aylantirish qotadi.
Bu ikki muammoga ikki xil dori bor:
- Qidiruvga — kutish: mehmon yozishdan to'xtagandagina so'rash. Bu debounce ("tinchlantirish").
- Chiziqqa — siyraklashtirish: aylantirish davom etsa ham, masalan, har 100 ms da bir marta yangilash. Bu throttle.
Debounce bilan tanishsiz. Fokus, scroll va resize darsida uni resize uchun yozgan edik, Closure amaliyotda darsida esa qayta ishlatiladigan tinchlantir funksiyasiga aylantirgan edik. O'shanda "egizagi — throttle, ikkalasini to'liq keyin ko'ramiz" degan edik. Mana o'sha dars. O'tgan darsda o'rgangan requestAnimationFrame esa throttle'ning eng foydali turlaridan biriga aylanadi.
Bundan buyon nomlar inglizcha: debounce, throttle — real loyihalar va kutubxonalarda shunday.
2. Debounce: "to'xtagach, bir marta"
2.1 Eslab olamiz
Fokus, scroll va resize darsidagi o'xshatish — lift eshigi. Odamlar kirib turibdi — eshik har safar qayta ochiladi, taymeri boshidan boshlanadi. Hech kim kirmay, belgilangan vaqt o'tgandagina lift jo'naydi.
Kodda: har chaqiruv eski taymerni bekor qiladi va yangisini qo'yadi. Taymer faqat jimlik bo'lganda "portlaydi":
function debounce(fn, wait) {
let timerId;
return (...args) => {
clearTimeout(timerId);
timerId = setTimeout(() => fn(...args), wait);
};
}
const search = debounce((query) => {
console.log(`Qidiruv: ${query}`);
}, 200);
// Mehmon tez yozyapti: har 50 ms da bitta harf
["o", "os", "osh"].forEach((query, i) => {
setTimeout(() => search(query), i * 50);
});Konsolda:
Qidiruv: oshUchta chaqiruv — bitta bajarilish, oxirgi argument bilan. Klaviatura o'rniga harflarni taymerlar bilan "yozdik": ular 0, 50 va 100 ms da keladi. timerId barcha chaqiruvlar uchun umumiy — u debounce chaqiruvining muhitida, closure da yashaydi.
2.2 Vaqt chizig'ida
Qadamlarni kuzating. O'ng tomonda — kutayotgan taymer. Har yangi harf uni bekor qilib, yangisini qo'yadi:
Diqqat qiling: natija oxirgi harfdan 200 ms keyin keldi, birinchisidan emas. Mehmon uzoq yozsa, natija ham shuncha kechikadi. Bu debounce'ning tabiati — u "to'lqin" tugashini kutadi. To'lqin deb oralarida jimlik bo'lmagan ketma-ket chaqiruvlar to'dasini aytamiz — bu darsda bu so'z ko'p uchraydi.
Tekshirib ko'ring: Debounce 300 ms bilan sozlangan. Mehmon 2 soniya davomida har 100 ms da bitta harf yozdi. Funksiya necha marta va qachon bajariladi?
Javob
Bir marta — oxirgi harfdan 300 ms keyin, ya'ni taxminan 2,3-soniyada. Harflar orasidagi 100 ms hech qachon 300 ms jimlikka yetmaydi, shuning uchun har harf taymerni qayta boshlaydi.
3. «Bahor» qidiruvi: debounce + AbortController
3.1 Jonli misol
Endi haqiqiy qidiruv. Menyu mashq API dan keladi: GET https://ilmhamroh.uz/api/mashq/menyu?kechikish=200 — kechikish sekin internetni taqlid qiladi. Serverda qidiruv yo'q, shuning uchun ro'yxatni olib, brauzerda filtrlaymiz. Maydon ostida ikki hisoblagich: nechta harf yozildi va nechta so'rov ketdi:
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
input { font: inherit; padding: 0.3rem 0.5rem; width: 14rem; }
.stats { color: #555; }
</style>
<label for="q">Menyudan qidirish:</label><br>
<input id="q" type="search" autocomplete="off">
<p class="stats" id="stats">Harflar: 0 · So'rovlar: 0</p>
<ul id="results"></ul>
<script>
const API = "https://ilmhamroh.uz/api/mashq/menyu?kechikish=200";
const input = document.getElementById("q");
const stats = document.getElementById("stats");
const results = document.getElementById("results");
let keys = 0;
let requests = 0;
let controller = null;
function debounce(fn, wait) {
let timerId;
return (...args) => {
clearTimeout(timerId);
timerId = setTimeout(() => fn(...args), wait);
};
}
async function searchMenu(query) {
// eski so'rov hali kelmagan bo'lsa — to'xtatamiz
controller?.abort();
controller = new AbortController();
requests++;
try {
const { signal } = controller;
const response = await fetch(API, { signal });
const menu = await response.json();
const q = query.trim().toLowerCase();
const found = menu.filter(
(d) => d.nom.toLowerCase().includes(q),
);
results.replaceChildren(...found.map((d) => {
const li = document.createElement("li");
li.textContent = `${d.nom} — ${d.narx} so'm`;
return li;
}));
console.log(`So'rov: "${query}" → ${found.length} ta`);
} catch (error) {
if (error.name !== "AbortError") throw error;
}
}
const debouncedSearch = debounce(searchMenu, 300);
input.addEventListener("input", () => {
keys++;
stats.textContent = `Harflar: ${keys} · So'rovlar: ${requests}`;
debouncedSearch(input.value);
});
</script>"manti" deb tez yozing va to'xtang. Konsolda bitta qator chiqadi: So'rov: "manti" → 1 ta. Hisoblagich esa Harflar: 5 ni ko'rsatadi. Endi sekin yozing — har harfdan keyin 300 ms dan ko'p kutib: har harf alohida so'rov bo'ladi. Debounce "sekin yozuvchi"ni tezlashtirmaydi, faqat "tez yozuvchi"ning ortiqcha so'rovlarini kesadi.
Biz Chrome 154 da "man" ni har 50 ms da bitta harf qilib yozdik: konsolda bitta qator — So'rov: "man" → 1 ta.
Uch narsaga qarang:
debouncedSearchtinglovchidan tashqarida, bir marta yaratildi. Ichida yaratilsa, har harf o'z taymerini olardi (Closure amaliyotda dagi tuzoq).So'rovlarhisoblagichi keyingi harfda yangilanadi — u so'rov ketgan paytda emas, keyingiinputda chiziladi. Haqiqiy loyihada "Qidirilmoqda…" holati ham qo'shiladi.- Taom nomi
textContentbilan yozildi — serverdan kelgan matn HTML bo'lib bajarilmaydi.
3.2 Nega AbortController ham kerak?
Bekor qilish: AbortController darsida aytgan edik: "debounce so'rovlarni kamaytiradi, abort esa baribir ketgan eskilarini to'xtatadi". Endi ikkalasi bir joyda.
Debounce 300 ms jimlikni kutadi. Lekin mehmon "man" deb yozib, 400 ms to'xtab, keyin "ti" ni qo'shsa — ikki so'rov ketadi. Birinchisi ("man") sekin kelsa, u ikkinchisidan ("manti") keyin kelib, ekrandagi to'g'ri natijani eskisi bilan almashtirib qo'yishi mumkin. Bu — race condition (poyga holati): javoblar so'rovlar tartibida emas, kim tezroq bo'lsa, shu tartibda keladi. controller?.abort() yangi so'rovdan oldin eskisini to'xtatadi, uning AbortError i esa jimgina yutiladi. Ikkalasi birga — "kam so'rov va doim oxirgi javob".
Tekshirib ko'ring: Debounce'ni olib tashlab, faqat
AbortControllerqoldirsak, "manti" ni tez yozganda nima o'zgaradi?
Javob
Ekrandagi natija baribir to'g'ri bo'ladi — har yangi harf eski so'rovni to'xtatadi va faqat oxirgisi chiziladi. Lekin 5 ta so'rov ketadi (4 tasi yo'lda uziladi). Server va internet trafigi ortiqcha ishlaydi. Abort natijani to'g'rilaydi, debounce esa ishni kamaytiradi.
4. cancel va flush
4.1 Ikki qo'shimcha tugma
Debounce kutayotgan paytda ikki xil vaziyat bo'ladi:
cancel("bekor qil") — kutayotgan chaqiruv endi kerak emas. Masalan, mehmon qidiruv maydonini tozaladi yoki oynani yopdi — 300 ms dan keyin eski so'z bo'yicha qidirish ma'nosiz.flush("hoziroq bajar") — kutish shart emas, kutayotgan chaqiruvni darhol bajarish kerak. Masalan, mehmon Enter bosdi — u qidirmoqchi, 300 ms kutishning hojati yo'q. Yoki sahifa yopilyapti — kutayotgan saqlash yo'qolmasin.
Closure amaliyotda darsining 3-mashqida bekorQil ni qo'shgan edingiz. Endi flush ham. Buning uchun kutayotgan argumentlarni ham eslab qolish kerak:
function debounce(fn, wait) {
let timerId;
let pendingArgs = null; // kutayotgan chaqiruv argumentlari
function flush() {
clearTimeout(timerId);
if (pendingArgs === null) return; // kutayotgan narsa yo'q
const args = pendingArgs;
pendingArgs = null;
fn(...args);
}
function cancel() {
clearTimeout(timerId);
pendingArgs = null;
}
function debounced(...args) {
pendingArgs = args;
clearTimeout(timerId);
timerId = setTimeout(flush, wait);
}
return Object.assign(debounced, { flush, cancel });
}
const search = debounce((query) => {
console.log(`Qidiruv: ${query}`);
}, 300);
search("o");
search("os");
search.flush(); // Enter bosildi — kutmasdan
search("m");
search.cancel(); // maydon tozalandi
search("manti");Konsolda:
Qidiruv: os
Qidiruv: mantiQatorma-qator:
pendingArgs— "kutayotgan chaqiruv bormi va qaysi argumentlar bilan".null— hech narsa kutmayapti.- Taymer endi to'g'ridan-to'g'ri
flushni chaqiradi: oddiy kutish tugashi ham — "hoziroq bajarish"ning o'zi. flushkutayotgan narsa bo'lmasa hech narsa qilmaydi. Shuning uchun uni bemalol "ehtiyot uchun" chaqirish mumkin.Object.assign(debounced, { flush, cancel })— funksiyaga ikki metod yopishtiradi: funksiya ham obyekt (IIFE va funksiya obyekt sifatida).
"o" va "os" — flush ularni darhol bajardi, faqat oxirgisini. "m" — bekor qilindi va hech qachon chiqmadi. "manti" — oddiy yo'l bilan, 300 ms dan keyin.
4.2 Sahifa yopilganda
flush ning eng muhim joyi — sahifadan chiqish. Foydalanuvchi forma izohini yozdi va 400 ms ichida tabni yopdi. Debounce bilan saqlash hali kutayotgan edi — taymer endi hech qachon ishlamaydi va oxirgi so'zlar yo'qoladi. Sahifa hayot sikli darsidagi pagehide hodisasida flush() chaqirilsa, kutayotgan saqlash darhol bajariladi.
const saveDraft = debounce((text) => {
localStorage.setItem("bron:izoh", text);
}, 400);
note.addEventListener("input", () => saveDraft(note.value));
addEventListener("pagehide", () => saveDraft.flush());Lodash — tayyor yordamchi funksiyalarning mashhur kutubxonasi (npm orqali o'rnatiladi). Undagi _.debounce ham aynan shu ikki metodga ega: .cancel() va .flush().
Tekshirib ko'ring: Forma yuborildi va maydon tozalandi. Shu paytda saqlash debounce bilan kutayotgan edi — eski matn bilan. Nima chaqirish kerak:
cancelyokiflush?
Javob
cancel. Aks holda 400 ms dan keyin kutayotgan saqlash bajariladi va hozirgina yuborilgan eski matnni xotiraga qaytarib yozadi — keyingi safar forma ochilganda u yana paydo bo'ladi. Metod flush esa aynan shu eski matnni darhol yozib qo'yardi.
5. Throttle: "ko'pi bilan har N ms da bir marta"
5.1 G'oya
Debounce'ning zaif joyi: hodisalar to'xtamasa, u hech qachon bajarilmaydi. Mehmon 5 soniya uzluksiz aylantirsa, debounce bilan yangilanadigan "o'qildi" chizig'i 5 soniya qimirlamaydi, keyin birdan sakraydi.
Throttle ("sekinlatish") boshqacha va'da beradi: hodisalar qanchalik ko'p kelmasin, funksiya ko'pi bilan har N ms da bir marta bajariladi. Uzluksiz aylantirishda chiziq har 100 ms da yangilanib boradi.
O'xshatish — avtobus bekati. Avtobus har 10 daqiqada keladi. Odamlar bekatga istalgan paytda kelaveradi, lekin avtobus ularni 10 daqiqada bir marta olib ketadi. Odamlar ko'paysa, avtobuslar ko'paymaydi.
5.2 Eng oddiy throttle
Oxirgi bajarilish vaqtini eslab qolamiz. Yangi chaqiruv kelganda: oxirgisidan wait ms o'tganmi? O'tgan bo'lsa — bajaramiz, bo'lmasa — tashlab yuboramiz:
function throttle(fn, wait) {
let lastTime = -Infinity;
return (...args) => {
const now = Date.now();
if (now - lastTime >= wait) {
lastTime = now;
fn(...args);
}
};
}
const report = throttle((n) => {
console.log(`Ishladi: ${n}-chaqiruv`);
}, 250);
// 8 ta chaqiruv: har 100 ms da bittadan (0, 100, ... 700 ms)
for (let n = 1; n <= 8; n++) {
setTimeout(() => report(n), (n - 1) * 100);
}Konsolda:
Ishladi: 1-chaqiruv
Ishladi: 4-chaqiruv
Ishladi: 7-chaqiruvlastTime = -Infinity— birinchi chaqiruvdanow - (-Infinity)cheksiz katta, demak birinchisi darhol bajariladi.- 1-chaqiruv (0 ms) bajarildi. 2-si (100 ms) va 3-si (200 ms) — 250 ms o'tmagan, tashlandi. 4-si (300 ms) — o'tdi, bajarildi. Keyin 7-si (600 ms).
- 8-chaqiruv (700 ms) — 7-sidan 100 ms keyin, tashlandi. Va bu muammo bo'lishi mumkin — keyingi bo'limda.
Debounce bilan solishtiring: xuddi shu 8 chaqiruvga 250 ms li debounce bitta natija berardi — 8-chi, 950-millisekundda. Vaqt chizig'ida (● — funksiya bajarildi):
vaqt, ms 0 100 200 300 400 500 600 700 800 900 950
chaqiruv 1 2 3 4 5 6 7 8
throttle ● ● ●
debounce ● (8)Throttle to'lqin davomida siyrak ishlaydi, debounce esa to'lqin tugagach bir marta.
5.3 Leading va trailing
Ikki atama kutubxonalarda va intervyularda doim chiqadi:
- Leading ("oldingi chet") — to'lqinning boshida bajarish. Yuqoridagi
throttle— leading: birinchi chaqiruv darhol ishladi. - Trailing ("orqa chet") — to'lqinning oxirida bajarish. Bizning birinchi
debounce— trailing: oxirgi chaqiruvdan keyin.
Oddiy throttle'ning muammosi — trailing yo'q: oxirgi chaqiruv (8-chi) tashlab yuborildi. Scroll uchun bu yomon: foydalanuvchi to'xtagan joydagi yakuniy holat chizilmay qoladi. "O'qildi" chizig'i 93% da to'xtaydi, sahifa esa 100% aylantirilgan. Yaxshi throttle leading va trailing'ni birga qiladi: birinchisi darhol, oxirgisi esa kechikib bo'lsa ham, albatta. Uni 3-mashqda yozasiz.
| Leading | Trailing | |
|---|---|---|
| Debounce | birinchi bosish darhol, keyingilari jimlikkacha e'tiborsiz (ikki marta bosish himoyasi) | oxirgi chaqiruv, jimlikdan keyin (qidiruv) — sukut |
| Throttle | birinchi darhol (tez javob) | oxirgi holat yo'qolmaydi (scroll, drag) |
6. Debounce, throttle va rAF — yonma-yon
6.1 Jonli taqqoslash
Bitta aylantiriladigan blok, to'rtta hisoblagich. Hammasi bitta scroll hodisasiga ulangan: xom tinglovchi, debounce (200 ms), throttle (200 ms) va rAF throttle (keyingi bo'limda). Blokni aylantiring — avval sekin, keyin tez, keyin to'xtang:
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
.feed { height: 140px; overflow-y: auto; border: 1px solid #ccd;
border-radius: 0.5rem; }
.long { height: 1500px; padding: 0.5rem;
background: repeating-linear-gradient(
#fff 0 40px, #eef3ee 40px 80px); }
dl { display: grid; grid-template-columns: auto auto; gap: 0 1rem;
justify-content: start; }
dd { margin: 0; font: 700 1rem ui-monospace, monospace; }
</style>
<div class="feed" id="feed">
<div class="long">Aylantiring ↓</div>
</div>
<dl>
<dt>scroll hodisalari</dt><dd id="raw">0</dd>
<dt>debounce 200 ms</dt><dd id="deb">0</dd>
<dt>throttle 200 ms</dt><dd id="thr">0</dd>
<dt>rAF throttle</dt><dd id="raf">0</dd>
</dl>
<script>
function debounce(fn, wait) {
let timerId;
return (...args) => {
clearTimeout(timerId);
timerId = setTimeout(() => fn(...args), wait);
};
}
function throttle(fn, wait) {
let lastTime = -Infinity;
return (...args) => {
const now = performance.now();
if (now - lastTime >= wait) {
lastTime = now;
fn(...args);
}
};
}
function rafThrottle(fn) {
let frameId = null;
let lastArgs;
return (...args) => {
lastArgs = args;
if (frameId !== null) return; // shu kadrga allaqachon so'raldi
frameId = requestAnimationFrame(() => {
frameId = null;
fn(...lastArgs);
});
};
}
const counts = { raw: 0, deb: 0, thr: 0, raf: 0 };
function bump(key) {
counts[key]++;
document.getElementById(key).textContent = counts[key];
}
const feed = document.getElementById("feed");
feed.addEventListener("scroll", () => bump("raw"));
feed.addEventListener("scroll", debounce(() => bump("deb"), 200));
feed.addEventListener("scroll", throttle(() => bump("thr"), 200));
feed.addEventListener("scroll", rafThrottle(() => bump("raf")));
</script>Nimani ko'rdingiz:
- scroll hodisalari — eng katta son. Brauzer har kadrda ko'pi bilan bitta
scrollberadi, lekin bu baribir soniyasiga 60–120 ta. - debounce — aylantirayotganda qimirlamaydi. To'xtaganingizdan 200 ms keyin 1 ga oshadi. "To'lqin" boshiga bitta.
- throttle — aylantirish davomida tekis o'sadi: soniyasiga taxminan 5 marta.
- rAF throttle — deyarli
scrollhodisalari bilan teng. Bu kutilgan hol, sababi keyingi bo'limda.
Biz Chrome 154 da blokni 100 marta, har 20 ms da 15 px dan aylantirdik: scroll 92, debounce 1, throttle 14, rAF throttle 92. Aniq sonlar kompyuter va ekranga bog'liq, nisbat esa doim shunday.
6.2 rAF throttle: "har kadrda bir marta"
rafThrottle — throttle, faqat "N ms" o'rniga "bitta kadr". Chaqiruv kelganda, shu kadr uchun rAF so'ralmagan bo'lsa — so'raymiz. So'ralgan bo'lsa — faqat argumentlarni yangilaymiz. Kadr kelganda funksiya oxirgi argumentlar bilan bir marta ishlaydi.
Nega u scroll bilan deyarli teng? Chunki Chrome scroll hodisasini o'zi ham kadrga bir marta beradi. Lekin pointermove, input, wheel kabi hodisalar bitta kadr ichida bir necha marta kelishi mumkin. Va asosiysi — rAF throttle ichidagi kod kadr chizilishidan oldin ishlaydi (requestAnimationFrame). DOM'ga yozish aynan shu kadrda ekranga chiqadi, ortiqcha yozish esa umuman bo'lmaydi.
Qachon qaysi biri:
| Vaziyat | Tanlov | Nega |
|---|---|---|
| Qidiruv, avtomatik tekshiruv | debounce (trailing), 250–400 ms | yozish tugashini kutish |
| Oyna yoki panel o'lchami — yakuniy natija | debounce, 150–250 ms | faqat oxirgi o'lcham kerak |
| Scroll'ga bog'liq vizual (chiziq, soya, sudrash) | rAF throttle | ekran bilan bir ritmda |
| Scroll'ga bog'liq og'ir ish (hisob, so'rov) | throttle 100–250 ms + trailing | siyrak, lekin oxirgisi yo'qolmaydi |
| «Buyurtma berish» ni ikki marta bosish | debounce (leading) yoki tugmani o'chirish | birinchisi darhol, qolgani e'tiborsiz |
| Formani qoralama sifatida saqlash | debounce + flush (pagehide da) |
kam yozish, hech narsa yo'qolmaydi |
Maslahat: Avval maxsus vosita bormi — tekshiring. "Element ko'rindimi?" —
IntersectionObserver(keyingi dars), element o'lchami —ResizeObserver(undan keyingisi), scroll'ga bog'liq animatsiya — CSS scroll-driven animatsiyalar. Ularscrolltinglovchisini umuman keraksiz qiladi.
Tekshirib ko'ring: Sichqoncha bilan elementni sudrash (
pointermove) paytida uningtransformi yangilanadi. 100 ms li throttle tanlansa, foydalanuvchi nimani sezadi?
Javob
Element sichqonchadan orqada qolib, "sakrab-sakrab" yuradi: soniyasiga atigi 10 marta yangilanadi, ekran esa 60–120 marta. Vizual uchun to'g'ri tanlov — rAF throttle: har kadrda bir marta, oxirgi koordinata bilan.
7. Leading debounce: ikki marta bosishdan himoya
Event obyekti va standart harakatlar darsida va'da bergan edik: mehmon «Buyurtma berish» ni sabrsizlanib uch marta bossa — uchta buyurtma ketmasin. Leading debounce aynan shu: birinchi bosish darhol bajariladi, keyingi bosishlar esa jimlik bo'lguncha e'tiborsiz qoladi:
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
button { font: inherit; padding: 0.5rem 1rem; }
</style>
<button id="order" type="button">Buyurtma berish</button>
<p id="info">Bosishlar: 0</p>
<script>
function debounceLeading(fn, wait) {
let timerId = null;
return (...args) => {
if (timerId === null) fn(...args); // to'lqin boshi — darhol
clearTimeout(timerId);
timerId = setTimeout(() => {
timerId = null; // jimlik bo'ldi — keyingi to'lqin
}, wait);
};
}
let clicks = 0;
let orderNumber = 0;
const sendOrder = debounceLeading(() => {
orderNumber++;
console.log(`Buyurtma #${orderNumber} yuborildi`);
}, 1000);
document.getElementById("order").addEventListener("click", () => {
clicks++;
const info = document.getElementById("info");
info.textContent = `Bosishlar: ${clicks}`;
sendOrder();
});
</script>Tugmani tez-tez uch marta bosing: Bosishlar: 3, konsolda esa bitta Buyurtma #1 yuborildi. 1 soniya kutib yana bossangiz — Buyurtma #2. Biz 100 ms oraliqda uch marta bosib tekshirdik: bitta buyurtma.
Bu throttle'dan nimasi bilan farq qiladi? Throttle (1000 ms) uzluksiz bosishda har soniyada bittadan buyurtma yuborardi. Leading debounce esa bosishlar to'xtamaguncha hech birini yubormaydi — birinchisidan boshqa.
Diqqat: Haqiqiy to'lov yoki buyurtma formasida faqat debounce'ga ishonmang. Ishonchli yo'l — yuborilayotganda tugmani o'chirish (
aria-disabledva "Yuborilmoqda…" — Dinamik UI'da qulaylik) va serverning o'zida takrorni tekshirish (idempotentlik — Asinxron naqshlar). Debounce — foydalanuvchi qulayligi, himoya emas.
8. Ko'p uchraydigan xatolar
8.1 Debounce'ni har hodisada qayta yaratish
// ❌ har harf yangi debounce — yangi taymer,
// hech narsa bekor qilinmaydi
input.addEventListener("input", () => {
debounce(searchMenu, 300)(input.value);
});Qatorni bo'lib o'qing: debounce(searchMenu, 300) yangi debounce'li funksiya yaratadi, keyingi (input.value) esa uni darhol chaqiradi. Har harf o'z debounce'ini, demak o'z timerId sini oladi — clearTimeout boshqa harfning taymerini ko'rmaydi. Natijada "manti" uchun 5 ta qidiruv — 300 ms kechikish bilan, lekin hammasi. Tuzatish: const debouncedSearch = debounce(searchMenu, 300); — tinglovchidan tashqarida, bir marta.
8.2 Vizual yangilanish uchun debounce
Scroll progress chizig'i debounce bilan — aylantirish davomida qotib turadi va oxirida sakraydi. Tuzatish: vizual uchun rAF throttle, "yakuniy natija" uchungina debounce.
8.3 Trailing'siz throttle bilan yakuniy holat
"O'qildi" chizig'i 93% da qoladi, chunki oxirgi scroll throttle oynasiga tushib, tashlab yuborildi. Tuzatish: trailing'li throttle (3-mashq) yoki rAF throttle (u oxirgi argumentni doim bajaradi).
8.4 Juda uzun kutish
Qidiruvda 1000 ms debounce — mehmon "ishlamayapti" deb o'ylaydi. Amaliyotda qidiruv uchun 250–400 ms qulay chegara hisoblanadi. Tuzatish: kichikroq wait va "Qidirilmoqda…" belgisi.
8.5 Kutayotgan chaqiruvni unutish
Element o'chirildi yoki forma yuborildi, debounce esa 300 ms dan keyin eski ma'lumot bilan ishlaydi — yo'q elementga yozadi yoki eski matnni saqlaydi. Tuzatish: keraksiz bo'lganda cancel(), sahifadan chiqishda flush() ("cancel va flush" bo'limi).
9. Mashqlar
1-mashq (oson): resize — yakuniy o'lcham
Fokus, scroll va resize darsidagi resize misolini yangi debounce bilan qayta yozing: window ning resize hodisasida 250 ms jimlikdan keyin konsolga Yakuniy eni: N px chiqsin. Nechta resize kelganini ham sanang.
Ishora: "Debounce: to'xtagach, bir marta" bo'limidagi debounce ni nusxalang; hisoblagich tinglovchida oshadi, debounce'li funksiyada chiqariladi va nolga tushadi.
Yechim
<p>Ekran tugmalarini bosing yoki kompyuterda oynani sudrang.</p>
<script>
function debounce(fn, wait) {
let timerId;
return (...args) => {
clearTimeout(timerId);
timerId = setTimeout(() => fn(...args), wait);
};
}
let events = 0;
const reportWidth = debounce(() => {
const width = innerWidth;
console.log(`Yakuniy eni: ${width} px (${events} ta resize)`);
events = 0;
}, 250);
addEventListener("resize", () => {
events++;
reportWidth();
});
</script>Ekran tugmalari kenglikni bir zumda o'zgartiradi — 1 ta resize. Kompyuterda brauzer oynasini qo'lda sudrasangiz, qavs ichidagi son o'nlab bo'ladi, qator esa bitta.
2-mashq (o'rta): "O'qildi" chizig'i — rAF throttle bilan
Aylantiriladigan blok tepasida chiziq bo'lsin: blok qancha aylantirilganiga qarab 0% dan 100% gacha to'lsin. Hisob: feed.scrollTop / (feed.scrollHeight - feed.clientHeight) (Element o'lchamlari va scroll). Yangilash rafThrottle bilan bo'lsin, chiziq transform: scaleX(...) bilan cho'zilsin.
Yechim
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
.bar { height: 6px; background: #2e7d32; transform-origin: left;
transform: scaleX(0); }
.feed { height: 160px; overflow-y: auto; border: 1px solid #ccd; }
.long { height: 1200px; padding: 0.5rem; }
</style>
<div class="bar" id="bar"></div>
<div class="feed" id="feed">
<div class="long">«Bahor» tarixi…</div>
</div>
<script>
function rafThrottle(fn) {
let frameId = null;
let lastArgs;
return (...args) => {
lastArgs = args;
if (frameId !== null) return;
frameId = requestAnimationFrame(() => {
frameId = null;
fn(...lastArgs);
});
};
}
const feed = document.getElementById("feed");
const bar = document.getElementById("bar");
const updateBar = rafThrottle(() => {
const max = feed.scrollHeight - feed.clientHeight;
const progress = max > 0 ? feed.scrollTop / max : 0;
bar.style.transform = `scaleX(${progress})`;
});
feed.addEventListener("scroll", updateBar);
</script>scaleX — Rendering pipeline darsidagi arzon transform: layout ham, paint ham yo'q. Blok oxirigacha aylantirilganda chiziq aniq to'liq bo'ladi — rAF throttle oxirgi scroll ni tashlab yubormaydi.
3-mashq (qiyin): Trailing'li throttle
"Throttle" bo'limidagi throttle ni shunday o'zgartiringki, oynaga tushib qolgan oxirgi chaqiruv yo'qolmasin: u qolgan vaqt o'tgach, eng oxirgi argumentlar bilan bajarilsin. Birinchi chaqiruv baribir darhol bajarilsin (leading + trailing).
Sinov: o'sha 8 ta chaqiruv (har 100 ms da), wait = 270. Kutilgan natija: 1, 3, 6 va 8-chaqiruvlar.
Ishora: oxirgi argumentlarni lastArgs da saqlang. "Qolgan vaqt" = wait - (hozir - lastTime). Qolgan vaqt musbat bo'lsa va taymer yo'q bo'lsa — setTimeout(run, qolganVaqt).
Yechim
function throttle(fn, wait) {
let lastTime = -Infinity;
let timerId = null;
let lastArgs = null;
function run() {
lastTime = Date.now();
timerId = null;
const args = lastArgs;
lastArgs = null;
fn(...args);
}
return (...args) => {
lastArgs = args;
const remaining = wait - (Date.now() - lastTime);
if (remaining <= 0) {
clearTimeout(timerId);
run();
} else if (timerId === null) {
timerId = setTimeout(run, remaining);
}
};
}
const report = throttle((n) => {
console.log(`Ishladi: ${n}-chaqiruv`);
}, 270);
for (let n = 1; n <= 8; n++) {
setTimeout(() => report(n), (n - 1) * 100);
}Konsolda:
Ishladi: 1-chaqiruv
Ishladi: 3-chaqiruv
Ishladi: 6-chaqiruv
Ishladi: 8-chaqiruvVaqt bo'yicha: 0 ms — 1-chaqiruv darhol. 100 ms — 170 ms qoldi, taymer 270 ms ga qo'yildi. 200 ms da 3-chaqiruv faqat lastArgs ni yangiladi. 270 ms da taymer 3-chaqiruvni bajardi. Xuddi shunday 540 ms da 6-chaqiruv, 810 ms da esa oxirgi — 8-chaqiruv. U oddiy throttle'da yo'qolardi. Lodash'dagi _.throttle(fn, wait) sukut bo'yicha shunday ishlaydi: { leading: true, trailing: true }.
4-mashq: Vazifalar qadami — qoralama debounce bilan
vazifalar ga birinchi 13-qism qadami. "Yangi vazifa" maydoniga uzun matn yozdingiz va tasodifan sahifani yangiladingiz — matn yo'qoldi. Endi yozilayotgan matn qoralama sifatida localStorage ga saqlanadi va sahifa qayta ochilganda maydonga qaytadi.
Nega har harfda saqlamaymiz? localStorage.setItem — sinxron, diskka yozadi (localStorage va sessionStorage darsi). Har harfda yozish kiritishni sekinlatadi. Shuning uchun — debounce: yozish to'xtagach, 400 ms dan keyin bir marta. Qidiruv emas, aynan qoralama tanlangani sababi: vazifalar dagi qidiruv 14-qismga rejalashtirilgan.
Bu darsdagi uch g'oya birdaniga ishlatiladi: debounce, cancel (kanonda — bekor) va flush (kanonda — hoziroq, pagehide da). vazifalar kanoni o'zbekcha nomlar bilan yozilgan — mana shu uchun debounce bu yerda kechiktir.
1. Branch:
git switch -c feature/qoralama-debounce2. Yangi fayl assets/js/taymer.js (// @ts-check bilan; JSDoc izohlari bu yerda qisqartirilgan, to'liq fayl — kanon repoda):
// @ts-check
// taymer.js — tez-tez keladigan chaqiruvlarni kamaytirish
export function kechiktir(ishlovchi, kutish) {
let taymer;
let kutayotgan = null;
function hoziroq() {
clearTimeout(taymer);
if (kutayotgan === null) {
return;
}
const argumentlar = kutayotgan;
kutayotgan = null;
ishlovchi(...argumentlar);
}
function bekor() {
clearTimeout(taymer);
kutayotgan = null;
}
function kechiktirilgan(...argumentlar) {
kutayotgan = argumentlar;
clearTimeout(taymer);
taymer = setTimeout(hoziroq, kutish);
}
return Object.assign(kechiktirilgan, { hoziroq, bekor });
}"cancel va flush" bo'limidagi debounce bilan solishtiring — tuzilma bir xil: kutayotgan = pendingArgs, hoziroq = flush, bekor = cancel. Fayl jsconfig.json ning include ro'yxatiga qo'shiladi — npm run tip uni ham tekshirsin.
3. saqlash.js — qoralama uchun ikki funksiya (kalit "vazifalar:qoralama"):
const QORALAMA_KALITI = "vazifalar:qoralama";
// Yozib tugatilmagan vazifa matni: sahifa yangilansa ham yo'qolmasin
export function qoralamaniSaqla(matn) {
try {
if (matn === "") {
localStorage.removeItem(QORALAMA_KALITI);
} else {
localStorage.setItem(QORALAMA_KALITI, matn);
}
} catch (xato) {
console.warn(`Qoralama saqlanmadi: ${xato.name}`);
}
}
export function qoralamaniOl() {
try {
return localStorage.getItem(QORALAMA_KALITI) ?? "";
} catch {
return ""; // xotira yopiq — qoralamasiz ochiladi
}
}Bo'sh matn — kalitni o'chiradi: bo'sh qoralamani saqlashning ma'nosi yo'q. try/catch — saqla dagi kabi: xotira yopiq yoki to'la bo'lsa, ilova yiqilmaydi.
4. asosiy.js — to'rt joy:
import { kechiktir } from "./taymer.js";
// Qoralama har harfda emas, yozish to'xtagach bir marta saqlanadi:
// localStorage sinxron — har harfda yozish kiritishni sekinlatadi
const QORALAMA_KUTISH_MS = 400;
const qoralamaniKechiktir = kechiktir(
qoralamaniSaqla,
QORALAMA_KUTISH_MS,
);
// yangiVazifaniQosh ichida, muvaffaqiyatli qo'shishdan keyin:
forma.reset();
// Kutayotgan saqlash eski matnni qaytarib yozmasin
qoralamaniKechiktir.bekor();
qoralamaniSaqla("");
// maydon input tinglovchisining birinchi qatori:
qoralamaniKechiktir(maydon.value);
// Sahifa yopilsa yoki yangilansa — kutib turgan qoralama yo'qolmasin
addEventListener("pagehide", () => qoralamaniKechiktir.hoziroq());
// Ishga tushirishda, render() dan oldin:
maydon.value = qoralamaniOl();Har birining sababi darsda bor: qoralamaniKechiktir bir marta, tinglovchidan tashqarida yaratildi ("Debounce'ni har hodisada qayta yaratish" xatosi). Vazifa qo'shilgach — bekor(): aks holda 400 ms dan keyin kutayotgan saqlash eski matnni qaytarib yozardi ("cancel va flush" bo'limidagi savol). pagehide da hoziroq() — tab yopilsa ham oxirgi harflar yo'qolmaydi.
index.html ga yana bitta qator — boshqa modullar kabi oldindan yuklash:
<link rel="modulepreload" href="assets/js/taymer.js">5. Testlar. tekshiruv/taymer.test.js — 5 ta test, node:test ning mock.timers soxta soati bilan: setTimeout haqiqiy 400 ms ni kutmaydi, mock.timers.tick(400) bilan "suriladi". Tekshiriladi: faqat oxirgisi bajariladi, muddatdan oldin bajarilmaydi, hoziroq, bo'sh hoziroq, bekor. saqlash.test.js ga +2 test. Natija — 66/66 (13 suite); lint, format:check, tip — toza.
6. Brauzerda tekshirish (Live Server, kanon tekshiruvidagi to'rt qator — 49/49 ning bir qismi):
✅ qoralama: darhol yozilmaydi: null
✅ qoralama: 400 ms dan keyin: "Qoralama matni"
✅ qoralama: pagehide da darhol saqlandi: "Qoralama matni 2"
✅ qoralama: bo'shatilsa o'chadi: nullO'zingiz ham sinang: maydonga yozing va DevTools → Application → Local Storage da vazifalar:qoralama kalitini kuzating. Yozayotganda u o'zgarmaydi, to'xtaganingizdan 400 ms keyin yangilanadi. Sahifani yangilang — matn maydonda.
7. Commit va PR. To'liq xabar 89 belgi — Git sarlavhasi uchun uzun (≤ 72). Shuning uchun ikkiga bo'lamiz — sarlavha va tana:
git add assets/js/taymer.js assets/js/saqlash.js assets/js/asosiy.js \
index.html jsconfig.json tekshiruv/taymer.test.js \
tekshiruv/saqlash.test.js
git commit \
-m "feat: yozilayotgan vazifa matni qoralama sifatida saqlanadi" \
-m "kechiktir (debounce) 400 ms"
git push -u origin feature/qoralama-debounce
gh pr create --fill
gh pr merge --mergeDiff: 7 fayl, +180 −2. Throttle kanonga kirmadi — vazifalar da unga tabiiy joy yo'q, u faqat shu darsda.
10. Real ishda
- Qidiruv va avtoto'ldirish. Google, Yandex, internet do'konlar, Telegram — debounce + bekor qilish. Har harfga emas, har so'zga taxminan bitta so'rov: bizning "manti" misolida 5 o'rniga 1.
- Kutubxonalar. Lodash (4.18.1, 2026-oktabr) —
_.debounce(fn, wait, { leading, trailing, maxWait })va_.throttle,.cancel(),.flush()bilan.maxWait— "debounce, lekin har N ms da kamida bir marta" — throttle aslida shu bilan yozilgan. Kichik loyihada bitta funksiya uchun butun kutubxonani ulash shart emas — bugungi 15 qatorli kod yetarli. React'da ham "debounce qilingan qiymat" hook'lari keng tarqalgan (React — 17-qismda). - Avtomatik saqlash. Google Docs, Notion, forma qoralamalari — debounce bilan saqlash va sahifadan chiqishda
flush. - Analitika va API cheklovlari. Scroll chuqurligi, sichqoncha harakati — throttle bilan yuboriladi. Serverlardagi "daqiqasiga 120 so'rov" kabi cheklovlar (rate limiting) — throttle'ning server tomondagi qarindoshi.
- Intervyu. "Debounce va throttle farqi?", "
debounceni yozing", "leading va trailing nima?" — frontend intervyularining eng ko'p beriladigan amaliy savollaridan.
Xulosa
- Debounce — jimlikdan keyin bir marta, oxirgi argument bilan (trailing); leading varianti — birinchisi darhol, qolgani jimlikkacha e'tiborsiz.
- Throttle — ko'pi bilan har N ms da bir marta; yaxshi throttle leading + trailing.
- rAF throttle — har kadrda bir marta; scroll va sichqonchaga bog'liq vizual uchun.
- Qidiruvda debounce +
AbortController: kam so'rov va doim oxirgi javob. cancel— kutayotganni tashlash;flush— kutayotganni hoziroq bajarish (pagehideda).- Debounce'li funksiya bir marta, tinglovchidan tashqarida yaratiladi.
Keyingi dars: IntersectionObserver — "element ko'rindimi?" savoliga scroll tinglovchisisiz javob: lazy loading, cheksiz lenta va paydo bo'lish animatsiyasi.
Manbalar
- MDN: "Debounce", "Throttle" (Glossary) — developer.mozilla.org
- Lodash hujjatlari:
_.debounce,_.throttle— lodash.com/docs - CSS-Tricks: David Corbacho, "Debouncing and Throttling Explained Through Examples" — css-tricks.com
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!