Performance — debounce, throttle, memoization
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
requestAnimationFrameyaxshiroq: u brauzerning ekran yangilanishiga (odatda 60 fps) moslashadi. "Element ko'rindimi?" savoli uchun esa scroll tinglash o'rnigaIntersectionObserverishlating.
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
WeakMapishlating.
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
AbortControllerbilan 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