Debounce
Debounce texnikasi: closure va setTimeout/clearTimeout orqali tez-tez ishga tushadigan hodisalarni (input, scroll, resize) qanday cheklashi, leading vs trailing debounce, va V8'dagi xotira/hidden class oqibatlari.
66-dars
Debounce
Debounce — bir funksiyani ketma-ket, tez-tez chaqirilishining oldini olib, faqat chaqiruvlar orasida ma'lum vaqt tinchlik (pauza) bo'lgandan keyin, bitta marta ishga tushirish texnikasi.
function debounce(fn, kutish) {
let timerId;
return function (...args) {
clearTimeout(timerId); // oldingi rejalashtirilgan chaqiruvni bekor qilamiz
timerId = setTimeout(() => {
fn.apply(this, args); // faqat "tinchlik" bo'lsa, chaqiramiz
}, kutish);
};
}
const izlash = debounce((matn) => {
console.log("Serverga so'rov:", matn);
}, 500);
input.addEventListener('input', (e) => izlash(e.target.value));
Foydalanuvchi "javascript" so'zini yozayotganda, input hodisasi har harf uchun (j, ja, jav, ...) alohida-alohida ishga tushadi. Debounce'siz — bu 10 marta serverga so'rov degani. Debounce bilan — faqat foydalanuvchi yozishni to'xtatgandan 500ms keyin, bitta marta so'rov ketadi.
1. Nega bu kerak?
Ba'zi brauzer hodisalari juda tez-tez ishga tushadi:
input/keyup— har harf bosilgandascroll— sekundiga o'nlab-yuzlab martaresize— oyna o'lchami o'zgarayotganda, sekundiga ko'p martamousemove— sichqoncha harakatlanganda, deyarli uzluksiz
Agar shu hodisaga bog'langan handler og'ir ish qilsa (server so'rovi, DOM'ni qayta hisoblash, katta hisob-kitob) — bu ishlash tezligini keskin pasaytiradi yoki keraksiz tarmoq trafigini yaratadi. Debounce — bu muammoni "faqat oxirgi chaqiruv muhim" degan mantiq bilan hal qiladi.
2. Ichki mexanizm — closure va timer orqali qanday ishlaydi
Debounce'ning yuragi — closure va setTimeout/clearTimeout (Web API):
function debounce(fn, kutish) {
let timerId; // BU o'zgaruvchi closure orqali qaytarilgan funksiya ichida "yashaydi"
return function (...args) {
clearTimeout(timerId);
timerId = setTimeout(() => fn.apply(this, args), kutish);
};
}
timerId — tashqi debounce funksiyasining lexical scope'ida yashaydi, va qaytarilgan ichki funksiya har safar chaqirilganda xuddi shu timerIdga murojaat qiladi (chunki u yopilgan closure). Aynan shu closure tufayli har bir chaqiruv avvalgi rejalashtirilgan setTimeoutni bekor qila oladi:
Chaqiruv 1 (t=0ms): setTimeout rejalashtirildi → t=500ms'da ishlaydi (timerId = A)
Chaqiruv 2 (t=100ms): clearTimeout(A) — bekor!
yangi setTimeout → t=600ms'da ishlaydi (timerId = B)
Chaqiruv 3 (t=150ms): clearTimeout(B) — bekor!
yangi setTimeout → t=650ms'da ishlaydi (timerId = C)
... (foydalanuvchi yozishda davom etyapti) ...
Chaqiruv N (t=400ms): clearTimeout — bekor!
yangi setTimeout → t=900ms'da ishlaydi
[TINCHLIK — 500ms davomida hech qanday chaqiruv bo'lmadi]
t=900ms: setTimeout ishga tushadi → fn() BAJARILADI
Event Loop nuqtai nazaridan: har bir setTimeout — macrotask sifatida Web API tomonidan Task Queue'ga qo'yiladi. clearTimeout — hali navbatga (queue) yetib bormagan macrotask'ni Web API darajasida bekor qiladi, u hech qachon Call Stack'ga tushmaydi. Shuning uchun debounce — macrotask'larni boshqarish orqali ishlaydigan texnika, deb ham aytish mumkin.
3. Leading vs Trailing debounce
Yuqoridagi versiya — trailing debounce: funksiya faqat oxirida (tinchlikdan keyin) chaqiriladi. Ba'zan leading (boshida darhol chaqirish, keyin qolganini e'tiborsiz qoldirish) kerak bo'ladi:
function debounce(fn, kutish, { leading = false } = {}) {
let timerId;
return function (...args) {
const chaqirishKerak = leading && !timerId;
clearTimeout(timerId);
timerId = setTimeout(() => {
timerId = null;
}, kutish);
if (chaqirishKerak) {
fn.apply(this, args);
}
};
}
Leading foydali holat: tugmani bir necha marta tez bosishning oldini olish (birinchi bosishda darhol reaksiya, keyingi bosishlar — kutish davri tugagunicha e'tiborsiz).
4. V8 darajasida — bog'liq narsalar
1. Xotira nuqtai nazaridan (Memory leak bilan bog'liq): Agar debounce orqali yaratilgan funksiya uzoq umr ko'radigan joyga (masalan, window yoki uzoq yashaydigan komponentga) bog'lansa va hech qachon tozalanmasa — closure ichidagi timerId va unga bog'liq butun lexical environment xotirada qolib ketishi mumkin. Shuning uchun komponent yo'q qilinganda (destroy/unmount), oxirgi kutilayotgan setTimeoutni clearTimeout bilan majburan bekor qilish — memory leak'ning oldini oladi.
2. Hidden class nuqtai nazaridan: debounce(fn, kutish) chaqirilganda, V8 har safar yangi closure obyekti yaratadi (chunki har bir chaqiruv o'z timerIdsiga ega bo'lishi kerak). Agar debounce funksiyasi juda ko'p marta, turli argumentlar bilan yaratilsa (masalan, render har safarida qaytadan debounce(...) chaqirilsa) — bu keraksiz obyekt yaratish degani, GC'ga ortiqcha yuk beradi. Shuning uchun debounce'langan funksiyani komponent tashqarisida yoki bir marta yaratish tavsiya etiladi, har render'da qayta emas (bu — React kabi kutubxonalarda tez-tez uchraydigan xato).
Qachon ishlatiladi (amaliy xulosa)
| Holat | Nega debounce |
|---|---|
| Search input (avtomatik qidiruv) | Har harfda emas, yozish tugagach so'rov |
| Form validatsiyasi (real-time) | Har bosishda emas, foydalanuvchi to'xtagach tekshirish |
| Window resize'da layout qayta hisoblash | Oyna cho'zilayotganda emas, tugagach bir marta |
| Auto-save (qoralama saqlash) | Har o'zgarishda emas, yozish to'xtagach saqlash |