- Massiv,
MapvəSetəməliyyatlarının Böyük O dəyərini qiymətləndirmək və uyğun verilən strukturunu seçmək - Bahalı funksiyaların nə qədər tez-tez işlədiyini debounce, throttle və memoizasiya ilə məhdudlaşdırmaq
- Zibil yığımını əlçatanlıq baxımından izah etmək və tipik yaddaş sızmalarından qaçmaq
Axtarış xanası hər hərfdə serverə sorğu göndərir — on hərf, on sorğu. 50 000 istifadəçinin siyahısında «bu e-poçt məşğuldurmu?» yoxlaması hər dəfə bütün massivi gəzir. Səhifə ləngiyir, telefon qızır. Performans adətən kiçik fəndlərdən yox, iki şeydən asılıdır: düzgün verilən strukturunu seçmək və artıq iş görməmək.
Böyük O təcrübədə
Böyük O qeydi işin giriş verilənlərinin ölçüsü n artdıqca necə böyüdüyünü göstərir, sabit əmsalları nəzərə almır. O(1) — n-dən asılı deyil; O(log n) — ikili axtarış; O(n) — bir keçid; O(n log n) — yaxşı sıralama; O(n²) — eyni verilənlər üzərində iç-içə dövr. n = 100 000 olanda O(n) yüz min addım, O(n²) isə on milyard addım deməkdir.
| Əməliyyat | Dəyəri |
|---|---|
arr[i], arr.push(x), arr.pop() | O(1) |
arr.shift(), arr.unshift(x) | ümumi halda O(n): elementlər yerini dəyişir |
arr.includes(x), indexOf, find | O(n) |
arr.sort() | O(n log n) |
map.get/set/has, set.add/has/delete | orta hesabla O(1) |
obj[key] | orta hesabla O(1) |
function hasDuplicatesSlow(items) {
let steps = 0;
for (let i = 0; i < items.length; i++) {
for (let j = i + 1; j < items.length; j++) {
steps++;
if (items[i] === items[j]) return { found: true, steps };
}
}
return { found: false, steps };
}
function hasDuplicatesFast(items) {
const seen = new Set();
let steps = 0;
for (const item of items) {
steps++;
if (seen.has(item)) return { found: true, steps };
seen.add(item);
}
return { found: false, steps };
}
const ids = Array.from({ length: 2000 }, (_, i) => i);
console.log(hasDuplicatesSlow(ids));
console.log(hasDuplicatesFast(ids));▸ Gözlənilən nəticə
{ found: false, steps: 1999000 }
{ found: false, steps: 2000 }Set isə 2000. Verilənlər 10 dəfə artsa, birinci variant 100 dəfə, ikincisi cəmi 10 dəfə yavaşıyar.Optimallaşdırmadan əvvəl ölç. performance.now() millisaniyəni kəsr hissəsi ilə verir, DevTools-un Performance paneli isə vaxtın harada getdiyini göstərir. Aşağıdakı kodu işə sal: rəqəmlər hər cihazda fərqli olacaq, amma nisbət aydın görünür.
const n = 20000;
const list = Array.from({ length: n }, (_, i) => i);
const set = new Set(list);
let start = performance.now();
for (let i = 0; i < n; i++) list.includes(i);
console.log('Array.includes:', Math.round(performance.now() - start), 'ms');
start = performance.now();
for (let i = 0; i < n; i++) set.has(i);
console.log('Set.has:', Math.round(performance.now() - start), 'ms');Debounce və throttle
Bəzi hadisələr çox tez-tez baş verir: input hər hərfdə, scroll və resize isə saniyədə onlarla dəfə. Debounce hadisələr X ms dayanana qədər gözləyir və sonra funksiyanı bir dəfə çağırır — axtarış xanası, avtomatik saxlama. Throttle funksiyanı hər X ms-də ən çoxu bir dəfə çağırır — sürüşdürmə mövqeyi, düyməyə təkrar-təkrar basma.
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
const search = debounce((text) => console.log('Searching for:', text), 200);
search('j');
search('ja');
search('jav');
setTimeout(() => search('javascript'), 500);▸ Gözlənilən nəticə
Searching for: jav Searching for: javascript
function throttle(fn, interval) {
let waiting = false;
return (...args) => {
if (waiting) return;
fn(...args);
waiting = true;
setTimeout(() => {
waiting = false;
}, interval);
};
}
const handleScroll = throttle((y) => console.log('scroll handled at', y), 100);
[0, 30, 60, 90].forEach((y) => handleScroll(y));
setTimeout(() => [150, 180].forEach((y) => handleScroll(y)), 150);▸ Gözlənilən nəticə
scroll handled at 0 scroll handled at 150
| Texnika | Nə vaxt işləyir | Tipik istifadə |
|---|---|---|
| debounce | hadisələr dayandıqdan X ms sonra, bir dəfə | axtarış, formanın yoxlanması, avtomatik saxlama |
| throttle | hər X ms-də ən çoxu bir dəfə | sürüşdürmə, pəncərənin ölçüsü, oyunda atəş |
Memoizasiya
Memoizasiya təmiz funksiyanın nəticələrini arqumentlərə görə yadda saxlamaqdır: eyni arqumentlə ikinci çağırış hazır cavabı dərhal qaytarır. Bu, yaddaş hesabına sürət qazanmaqdır. Yalnız təmiz funksiyalar üçün işləyir, həddi olmayan keş isə sonsuz böyüyə bilər.
function memoize(fn) {
const cache = new Map();
return (n) => {
if (cache.has(n)) return cache.get(n);
const result = fn(n);
cache.set(n, result);
return result;
};
}
let calls = 0;
const slowFib = (n) => {
calls++;
return n < 2 ? n : slowFib(n - 1) + slowFib(n - 2);
};
console.log(slowFib(25), 'calls:', calls);
calls = 0;
const fastFib = memoize((n) => {
calls++;
return n < 2 ? n : fastFib(n - 1) + fastFib(n - 2);
});
console.log(fastFib(25), 'calls:', calls);
console.log(fastFib(25), 'calls:', calls);▸ Gözlənilən nəticə
75025 calls: 242785 75025 calls: 26 75025 calls: 26
n yalnız bir dəfə hesablanır.Zibil yığımı və yaddaş sızmaları
JavaScript yaddaşı özü idarə edir: zibil yığıcı (garbage collector) «köklərdən» — qlobal dəyişənlərdən, cari çağırış stekindən, aktiv qapanmalardan — artıq əlçatan olmayan obyektləri silir. Müasir mühərriklər «işarələ və sil» (mark-and-sweep) üsulundan istifadə edir: köklərdən çatılan hər şey işarələnir, qalanı təmizlənir. Sən zibil yığıcını çağırmırsan — sadəcə lazım olmayan istinadları saxlamamalısan.
- Unudulmuş taymerlər: dayandırılmayan
setIntervalqapanmanı və onun bütün verilənlərini yaşadır. - Silinmiş elementlərdə qalan hadisə dinləyiciləri və DOM-dan çıxarılsa da dəyişəndə saxlanan elementlər.
- Yalnız böyüyən qlobal keşlər və massivlər.
const visits = new WeakMap();
function track(user) {
visits.set(user, (visits.get(user) ?? 0) + 1);
}
let user = { name: 'Elvin' };
track(user);
track(user);
console.log(visits.get(user), visits.has(user));
user = null;
const timer = setInterval(() => console.log('tick'), 1000);
clearInterval(timer);
console.log('Interval cleared: nothing keeps the callback alive');▸ Gözlənilən nəticə
2 true Interval cleared: nothing keeps the callback alive
user = null-dan sonra obyekt əlçatmaz olur və zibil yığıcı onu WeakMap-dəki yazısı ilə birlikdə silə bilər — dəqiq anı isə mühərrik seçir. WeakMap-in açarları yalnız obyekt ola bilər və onu gəzmək olmur.memoize(fn) funksiyasını Map ilə yaz ki, slowSquare hər arqument üçün yalnız bir dəfə işləsin. Hazırkı versiya keş saxlamır — ona görə real çağırışların sayı 4-dür, 2 olmalıdır.
let calls = 0;
const slowSquare = (n) => {
calls++;
return n * n;
};
function memoize(fn) {
// add a cache
return (n) => fn(n);
}
const fastSquare = memoize(slowSquare);
console.log(fastSquare(9), fastSquare(9), fastSquare(4), fastSquare(9));
console.log('real calls:', calls);▸ Gözlənilən nəticə
81 81 16 81 real calls: 2
debounce(fn, delay) funksiyasını tamamla: qaytarılan funksiya son çağırışdan delay ms sonra fn-i yalnız bir dəfə çağırmalıdır. İndi o, heç nəyi gözləmir və hər hərfi saxlayır.
function debounce(fn, delay) {
// wait `delay` ms after the last call, then call fn once
return fn;
}
const save = debounce((text) => console.log('Saved:', text), 300);
save('H');
save('He');
save('Hel');
setTimeout(() => save('Hello'), 100);
setTimeout(() => save('Hello!'), 700);▸ Gözlənilən nəticə
Saved: Hello Saved: Hello!
Əsas fikirlər
- Böyük O işin verilənlərlə necə böyüdüyünü göstərir: massivdə axtarış O(n),
Map/Setaxtarışı orta hesabla O(1), eyni verilənlər üzərində iç-içə dövr O(n²). - Massivdə təkrar-təkrar axtarmaq əvəzinə
Setvə yaMap-i bir dəfə qur və ondan istifadə et; optimallaşdırmadan əvvəl ölç. - Debounce hadisələr dayanandan sonra bir dəfə işləyir, throttle isə hər intervalda ən çoxu bir dəfə.
- Memoizasiya təmiz funksiyaların nəticələrini keşləyir — yaddaş hesabına sürət.
- Zibil yığıcı əlçatmaz obyektləri silir; sızmalar unudulmuş taymerlərdən, dinləyicilərdən və böyüyən keşlərdən yaranır,
WeakMapisə açarlarını yaşatmır.
Özünü yoxla
10 sual. Hər düzgün cavab XP qazandırır.