Series: JavaScript Basics Lesson 67

Throttle

Throttle texnikasi va debounce'dan farqi, timestamp-based va timer-based (trailing) implementatsiyalar, Event Loop/rendering bilan bog'liqligi, requestAnimationFrame asosidagi throttle, va qachon debounce, qachon throttle ishlatish kerakligi.

67-dars

Throttle

Throttle — bir funksiyaning tez-tez chaqirilishini cheklab, uni ma'lum vaqt oralig'ida ko'pi bilan bir marta ishga tushirish texnikasi — debounce'dan farqli, chaqiruvlar davom etayotgan bo'lsa ham, funksiya muntazam ravishda ishlab turadi, "tinchlikni" kutmaydi.

function throttle(fn, oraliq) {
  let oxirgiVaqt = 0;

  return function (...args) {
    const hozir = Date.now();

    if (hozir - oxirgiVaqt >= oraliq) {
      oxirgiVaqt = hozir;
      fn.apply(this, args);
    }
  };
}

const progressYangilash = throttle(() => {
  console.log("Scroll progress yangilandi:", window.scrollY);
}, 200);

window.addEventListener('scroll', progressYangilash);

Foydalanuvchi uzluksiz scroll qilayotganda, scroll hodisasi sekundiga o'nlab marta ishga tushadi. Throttle bilan — funksiya har 200ms'da ko'pi bilan bir marta chaqiriladi, garchi scroll uzluksiz davom etayotgan bo'lsa ham.


1. Debounce bilan solishtirish — bu yerda eng ko'p chalkashadigan joy

Bu ikkisi tashqi ko'rinishda o'xshash (ikkalasi ham "tez-tez chaqiriluvchi funksiyani cheklaydi"), lekin maqsad va natija butunlay farq qiladi:

Chaqiruvlar vaqti:     |--|--|--|--|--|--|--|--|--|--|--| (uzluksiz, 50ms oraliqda)
                       0  50 100 150 200 250 300 350 400 450 500

DEBOUNCE (500ms):      Hech narsa bajarilmaydi, chaqiruvlar davom etyapti...
                       ...faqat OXIRGI chaqiruvdan 500ms tinchlikdan keyin — 1 marta ishlaydi

THROTTLE (200ms):      t=0'da ishlaydi
                       t=200'da ishlaydi
                       t=400'da ishlaydi
                       (chaqiruvlar davom etsa ham, MUNTAZAM oraliqda ishlab turadi)

Debounce — "faqat foydalanuvchi to'xtagach bir marta bajar" (masalan, qidiruv so'rovi — oraliq natija kerak emas).

Throttle — "foydalanuvchi harakat qilayotganida ham, muntazam vaqt oralig'ida bajarib tur" (masalan, scroll paytida progress bar — foydalanuvchi ko'rib turishi kerak, faqat oxirida emas).

Agar scroll uchun debounce ishlatilsa — progress bar foydalanuvchi scroll qilib bo'lgandan keyingina bir marta yangilanadi, bu esa foydalanuvchi tajribasini yomonlashtiradi (u scroll qilib borayotganda hech narsa yangilanmayotganini ko'radi).


2. Ichki mexanizm — timestamp asosida (yuqoridagi versiya)

Yuqoridagi implementatsiya — "timestamp-based throttle":

  1. Har chaqiruvda joriy vaqt (Date.now()) oxirgi bajarilgan vaqt bilan solishtiriladi
  2. Agar farq belgilangan oraliqdan katta yoki teng bo'lsa — funksiya darhol bajariladi va oxirgiVaqt yangilanadi
  3. Aks holda — chaqiruv butunlay e'tiborsiz qoldiriladi (hech qanday timer rejalashtirilmaydi)

Bu — "leading" throttle: birinchi chaqiruv darhol ishlaydi, keyingi chaqiruvlar oraliq tugagunicha e'tiborsiz.

Muammosi: agar chaqiruvlar oraliq o'rtasida to'xtab qolsa (masalan, foydalanuvchi scroll qilib, t=150ms'da to'xtasa), oxirgi holat (t=150ms'dagi scroll pozitsiyasi) hech qachon ishlatilmaydi — chunki keyingi chaqiruv yo'q, funksiya faqat kelgusi chaqiruv bo'lganda ishga tushadi.


3. Timer-based throttle (trailing bilan)

Buni tuzatish uchun setTimeout asosidagi versiya ishlatiladi — bu debounce bilan ko'proq umumiylikka ega:

function throttle(fn, oraliq) {
  let kutilyapti = false;
  let oxirgiArgs = null;

  return function (...args) {
    oxirgiArgs = args;

    if (!kutilyapti) {
      kutilyapti = true;

      setTimeout(() => {
        fn.apply(this, oxirgiArgs); // OXIRGI holat bilan ishga tushadi
        kutilyapti = false;
      }, oraliq);
    }
  };
}

Bu yerda mantiq: birinchi chaqiruv timer'ni ishga tushiradi (kutilyapti = true). Timer tugaguncha kelgan barcha keyingi chaqiruvlar faqat oxirgiArgsni yangilaydi, lekin yangi timer yaratmaydi. Timer tugaganda — eng oxirgi argumentlar bilan fn ishga tushadi, va navbat qayta ochiladi. Bu — leading + trailing kombinatsiyasi: birinchi chaqiruv ham, oraliqdagi eng so'nggi holat ham hisobga olinadi.


4. Event Loop nuqtai nazaridan

scroll hodisasi — brauzer tomonidan juda tez-tez, ba'zan requestAnimationFrame tsikliga bog'liq holda ishga tushiriladi. Har bir hodisa handler chaqiruvi — Call Stack'da ishlaydigan sinxron kod. Throttle'siz, agar handler og'ir bo'lsa (masalan, DOM'ni o'qish — Reflow'ni majburlash), bu Call Stack'ni tez-tez band qilib, brauzerning rendering pipeline'iga (frame chizishga) vaqt qoldirmaydi — natija: "jerky" (silliq bo'lmagan) scroll, past FPS.

Throttle chaqiruvlar sonini kamaytirish orqali Call Stack'ni tezroq bo'shatadi, brauzerga har frame'da render qilish uchun ko'proq vaqt qoldiradi.

Muhim maslahat: vizual yangilanishlar (masalan, scroll asosida animatsiya) uchun setTimeout/timestamp asosidagi throttle o'rniga ko'pincha requestAnimationFrame ishlatish tavsiya etiladi — chunki u brauzerning rendering tsikli bilan sinxron ishlaydi (sekundiga taxminan 60 marta, ekran yangilanish chastotasiga mos), va hech qachon keraksiz ravishda tez-tez yoki kam-kam ishlamaydi.

function throttleRAF(fn) {
  let kutilyapti = false;

  return function (...args) {
    if (!kutilyapti) {
      kutilyapti = true;
      requestAnimationFrame(() => {
        fn.apply(this, args);
        kutilyapti = false;
      });
    }
  };
}

5. V8/GC nuqtai nazaridan — bog'liqlik

Debounce'da ko'rganimizdek, throttle ham closure orqali holatni (oxirgiVaqt, kutilyapti, oxirgiArgs) saqlaydi. Agar throttle'langan funksiya scroll/resize kabi global, uzoq yashovchi hodisaga bog'lansa va removeEventListener bilan hech qachon tozalanmasa — bu closure (va u ushlab turgan barcha o'zgaruvchilar) memory leak manbai bo'lib qolishi mumkin (aynan oldingi mavzuda ko'rgan "unutilgan event listener" holati).


Qachon nimasi ishlatiladi (amaliy xulosa)

Holat Texnika Sabab
Search input Debounce Faqat yozish tugagach so'rov kerak
Scroll'da progress bar Throttle Muntazam yangilanish ko'rinishi kerak
Infinite scroll (pastga yetganda yuklash) Throttle Muntazam tekshirish, lekin tez-tez emas
Button "double-click" himoyasi Throttle (leading) Birinchi bosish darhol, qolgani e'tiborsiz
Window resize'da layout hisoblash Debounce ko'pincha, ba'zan Throttle Foydalanuvchi tanlaviga bog'liq
Drag-and-drop pozitsiya yangilash Throttle (RAF bilan) Silliq vizual yangilanish kerak