Performance — debounce, throttle, memoization

~25 daq · JavaScript · Amaliy va Professional mavzular

Muammodan boshlaylik

Qidiruv maydoni:

input.addEventListener('input', async (e) => {
  const res = await fetch(`/api/qidiruv?q=${e.target.value}`);
  natijalarniChiz(await res.json());
});

Foydalanuvchi "televizor" deb yozdi — 9 ta so'rov: t, te, tel, ... Server ortiqcha yuklanadi, javoblar aralash tartibda kelib, ekranda eski natija qolishi mumkin.

Scroll hodisasi esa sekundiga 60–100 marta ishga tushadi. Unda og'ir hisob-kitob bo'lsa — sahifa "qotadi".


Debounce — tinchlanishni kutish

Debounce — funksiyani hodisalar to'xtagandan keyin ma'lum vaqt o'tib bir marta chaqirish.

function debounce(fn, kutish) {
  let taymer;
  return function (...args) {
    clearTimeout(taymer);
    taymer = setTimeout(() => fn.apply(this, args), kutish);
  };
}
const qidir = debounce(async (soz) => {
  const res = await fetch(`/api/qidiruv?q=${encodeURIComponent(soz)}`);
  natijalarniChiz(await res.json());
}, 300);

input.addEventListener('input', (e) => qidir(e.target.value));
Yozish:   t-e-l-e-v-i-z-o-r .............
Taymer:   ↺ ↺ ↺ ↺ ↺ ↺ ↺ ↺ ↺ ──300ms──▶ so'rov

9 ta so'rov o'rniga — 1 ta.

Qachon: qidiruv, avtomatik saqlash, resize dan keyin qayta hisoblash, forma validatsiyasi.


Throttle — tezlikni cheklash

Throttle — funksiyani ma'lum vaqt oralig'ida ko'pi bilan bir marta chaqirish.

function throttle(fn, oraliq) {
  let oxirgi = 0;
  let taymer;
  return function (...args) {
    const hozir = Date.now();
    const qolgan = oraliq - (hozir - oxirgi);
    if (qolgan <= 0) {
      oxirgi = hozir;
      fn.apply(this, args);
    } else {
      clearTimeout(taymer);
      taymer = setTimeout(() => {
        oxirgi = Date.now();
        fn.apply(this, args);
      }, qolgan);
    }
  };
}
window.addEventListener(
  'scroll',
  throttle(() => {
    yuqorigaTugma.hidden = window.scrollY < 500;
  }, 200),
);
Hodisalar: |||||||||||||||||||||||||||||||
Throttle:  ▲         ▲         ▲         ▲    (har 200 ms da)
Debounce:                                  ▲  (oxirida bir marta)
Debounce Throttle
Qachon chaqiradi Hodisalar to'xtagandan keyin Muntazam oraliqda
Doimiy hodisalar paytida Hech qachon Har N ms da
Misol Qidiruv, avtosaqlash Scroll, sichqoncha harakati, o'yin

[!TIP] Animatsiya va scroll ga bog'liq vizual yangilanishlar uchun requestAnimationFrame yaxshiroq: u brauzerning ekran yangilanishiga (odatda 60 fps) moslashadi. "Element ko'rindimi?" savoli uchun esa scroll tinglash o'rniga IntersectionObserver ishlating.


Memoization — natijani eslab qolish

Og'ir pure funksiya bir xil argument bilan qayta-qayta chaqirilsa — natijani keshlash:

function memoize(fn) {
  const kesh = new Map();
  return function (arg) {
    if (kesh.has(arg)) return kesh.get(arg);
    const natija = fn(arg);
    kesh.set(arg, natija);
    return natija;
  };
}
const fib = memoize((n) => (n < 2 ? n : fib(n - 1) + fib(n - 2)));

fib(50); // bir zumda — 12586269025

Memoize siz fib(50) — milliardlab chaqiruv; memoize bilan — 51 ta.

Bir nechta argument

function memoize(fn) {
  const kesh = new Map();
  return (...args) => {
    const kalit = JSON.stringify(args);
    if (!kesh.has(kalit)) kesh.set(kalit, fn(...args));
    return kesh.get(kalit);
  };
}

[!WARNING] Memoization faqat pure funksiyalar uchun (14-dars): natija faqat argumentlarga bog'liq bo'lishi kerak. Va kesh cheksiz o'sishi mumkin — ko'p xil argumentli funksiyalar uchun kesh hajmini cheklang (LRU) yoki obyekt argumentlar uchun WeakMap ishlating.


Eskirgan so'rovlarni bekor qilish

Debounce dan keyin ham sekin javob tezroq javobdan keyin kelishi mumkin:

let nazoratchi;

const qidir = debounce(async (soz) => {
  nazoratchi?.abort();
  nazoratchi = new AbortController();
  try {
    const res = await fetch(`/api/qidiruv?q=${encodeURIComponent(soz)}`, {
      signal: nazoratchi.signal,
    });
    natijalarniChiz(await res.json());
  } catch (e) {
    if (e.name !== 'AbortError') throw e;
  }
}, 300);

DOM bilan ishlash

// ❌ har iteratsiyada DOM — 1000 marta qayta hisoblash xavfi
for (const m of mahsulotlar) {
  royxat.innerHTML += `<li>${m.nomi}</li>`;
}

// ✅ bir marta
const fragment = document.createDocumentFragment();
for (const m of mahsulotlar) {
  const li = document.createElement('li');
  li.textContent = m.nomi;
  fragment.append(li);
}
royxat.append(fragment);
// ❌ o'qish va yozish aralash — majburiy layout (layout thrashing)
elementlar.forEach((el) => {
  el.style.height = el.offsetHeight + 10 + 'px';
});

// ✅ avval hammasini o'qish, keyin yozish
const balandliklar = elementlar.map((el) => el.offsetHeight);
elementlar.forEach((el, i) => {
  el.style.height = balandliklar[i] + 10 + 'px';
});

O'lchash

performance.mark('boshlash');
ogirIsh();
performance.mark('tugash');
performance.measure('ogir ish', 'boshlash', 'tugash');
console.log(performance.getEntriesByName('ogir ish')[0].duration);

DevTools → Performance → Record → harakat → Stop: qizil uchburchakli uzun vazifalar (50 ms dan ortiq) — qotishlar manbai.

[!INFO] Optimizatsiyadan oldin o'lchang. Ko'pincha sekinlik siz o'ylagan joyda emas. Taxmin bilan optimizatsiya qilish kodni murakkablashtiradi va natija bermaydi.


Nima uchun shunday?

Nima uchun hodisalar chastotasini cheklash bunchalik muhim?

Chunki brauzerda JavaScript bitta oqimda ishlaydi — u band bo'lganda sahifa bosishlarga javob bermaydi, animatsiyalar to'xtaydi. Hodisalar esa inson harakatiga qaraganda ancha tez-tez keladi: bitta scroll — o'nlab hodisa, bitta so'z — o'nlab input. Ularning har biriga to'liq ishlov berish keraksiz ish va resurs isrofi. Debounce va throttle "foydalanuvchi aslida nimani xohlayapti?" degan savolga javob beradi: qidiruvda — oxirgi so'zni, scroll da — vaqti-vaqti bilan holatni. Memoization esa bir marta qilingan ishni takrorlamaslik. Uchala texnika ham bir g'oyaga asoslanadi: eng tez kod — bajarilmagan kod.


Xulosa

Eslab qoling:

  • Debounce — hodisalar to'xtagandan keyin bir marta: qidiruv, avtosaqlash
  • Throttle — N ms da ko'pi bilan bir marta: scroll, mousemove
  • Vizual yangilanishlar — requestAnimationFrame; ko'rinish — IntersectionObserver
  • Memoization — pure funksiya natijasini keshlash; kesh hajmini nazorat qiling
  • Eskirgan so'rovlarni AbortController bilan bekor qiling
  • DOM: DocumentFragment, o'qish va yozishni ajratish
  • Avval o'lchang — performance.mark, DevTools Performance

Keyingi dars

84-dars: Security — XSS, CSRF va ulardan himoya