Mundarija (36)
- Bu darsda
- 1. Nega bu kerak?
- 2. Private holat
- 2.1 Muammo: ochiq son
- 2.2 Yechim: seyf va kassir
- 2.3 Ichki obyektni tashqariga bermang
- 3. Fabrika funksiya
- 3.1 Sozlamani bir marta berish
- 3.2 Fabrika obyekt ham qaytaradi
- 4. Bir martalik funksiya: once
- 4.1 Muammo: ikki marta to'lov
- 4.2 Qadamma-qadam
- 4.3 { once: true } dan farqi
- 5. Oddiy kesh: memoize
- 5.1 Bir xil savolga qayta-qayta hisob
- 5.2 Keshning uch sharti
- 6. Modul naqshi
- 6.1 IIFE + closure
- 6.2 Nega bu "tarixiy"?
- 7. debounce ichidan
- 7.1 Eslab olamiz
- 7.2 tinchlantir(fn, kutish)
- 7.3 Brauzerda: menyudan qidirish
- 8. Ko'p uchraydigan xatolar
- 8.1 Holatni qiymat sifatida qaytarish
- 8.2 Debounce'ni har hodisada qayta yasash
- 8.3 Toza bo'lmagan funksiyani keshlash
- 8.4 once da natijani unutish
- 9. Mashqlar
- 1-mashq (oson): Navbat hisoblagichi obyekt bilan
- 2-mashq (o'rta): Aksiya — faqat 3 ta mehmonga
- 3-mashq (qiyin): Bekor qilinadigan debounce
- 4-mashq: Vazifalar qadami — id generatorini ulang
- 10. Real ishda
- Xulosa
- Manbalar
JavaScript closure amaliyotda: private holat, fabrika, once, kesh va debounce
Qisqacha: Closure'ning to'rtta kundalik ishi bor. Yashirish — o'zgaruvchini faqat o'z funksiyalaringiz ko'radigan qilish (private holat). Sozlab yasash — sozlamani bir marta berib, tayyor funksiya olish (fabrika). Eslab qolish — "allaqachon chaqirilganmi?" (
once) yoki "bu javob hisoblanganmi?" (kesh). Vaqtni boshqarish — taymerni chaqiruvlar orasida saqlash (debounce). To'rttalasining siri bitta: kerakli o'zgaruvchi funksiyaning orqa cho'ntagida yashaydi.
Bu darsda
- Closure bilan private holat yasab, uni faqat ruxsat berilgan funksiyalar orqali o'zgartirasiz.
- Fabrika funksiya bilan bir xil qolipdagi funksiyalarni sozlab yasaysiz.
once(bir martalik funksiya) va oddiy kesh (memoize) ni o'zingiz yozasiz.- Modul naqshi nima ekanini va nega u tarixda kerak bo'lganini bilasiz.
- 09-qismdagi
debounceni ichidan tushunib, qayta ishlatiladigan funksiyaga aylantirasiz. vazifalarga id generatorini ulab,keyingiIdni holatdan yashirasiz.
Oldin bilishingiz kerak: Closure: funksiya o'z muhitini eslab qoladi, IIFE va funksiya obyekt sifatida, Spread va rest, Pure funksiya va side effect, Fokus, scroll va resize hodisalari.
1. Nega bu kerak?
O'tgan darsda closure'ning sirini ochdik: funksiya o'zi tug'ilgan muhitni "orqa cho'ntagida" olib yuradi. Bu chiroyli nazariya. Bugun savol boshqa: undan nima foyda?
Ikki eski va'da bor. IIFE va funksiya obyekt sifatida darsida taksi chaqiruvlarini taksiChaqir.soni xususiyatida sanagan edik va ogohlantirgan edik: bu son hammaga ochiq, istalgan kod taksiChaqir.soni = -999 deb yozib qo'yishi mumkin. "Closure esa hisoblagichni tashqaridan yashiradi" degan edik. Fokus, scroll va resize darsida esa debounce yozdik — let taymer ni tinglovchidan tashqarida e'lon qilib. U ishladi, lekin nega aynan shunday yozilishini ochmagan edik.
Bu darsda closure'ni to'rt xil ishda ko'ramiz:
| Ish | Closure nima saqlaydi | Misol |
|---|---|---|
| Yashirish | holatning o'zini | sovg'a karta balansi |
| Sozlab yasash | sozlamani | ish vaqti tekshiruvchisi |
| Eslab qolish | "bo'ldimi?", tayyor javoblar | once, kesh |
| Vaqtni boshqarish | taymer raqamini | debounce |
Hayotdan o'xshatish: kassadagi seyf. Pul ichkarida, uni hamma ko'radigan stol ustiga qo'yilmaydi. Seyfni faqat kassir ochadi va har ochishda qoidaga amal qiladi: "kvitansiyasiz pul berilmaydi". Closure — seyf, ichidagi o'zgaruvchi — pul, qaytarilgan funksiyalar esa — kassir. Tashqi kod pulni to'g'ridan-to'g'ri ololmaydi, faqat kassirdan so'raydi.
Dars oxirida vazifalar dagi keyingiId ni aynan shunday seyfga joylaymiz. O'tgan darsda uning qanday buzilishini o'z ko'zingiz bilan ko'rgansiz.
2. Private holat
2.1 Muammo: ochiq son
«Bahor» sovg'a kartalari sotadi: 100 000 so'mlik karta bilan mehmon bir necha marta ovqatlanishi mumkin. Oddiy obyekt bilan:
const karta = { balans: 100000 };
karta.balans -= 35000;
karta.balans -= 90000;
console.log(karta.balans); // -25000Balans manfiy bo'ldi — karta 25 000 so'm "qarzga" ishladi. Qoida ("balansdan ko'p yechib bo'lmaydi") hech qayerda yozilmagan. Yozsak ham, karta.balans ni o'zgartiradigan har bir joyda yozish kerak bo'ladi. Bittasida unutilsa — yana manfiy balans.
2.2 Yechim: seyf va kassir
Balansni closure ichiga yashiramiz. Tashqariga faqat ikki funksiya chiqadi:
function sovgaKartaYarat(boshlangich) {
let balans = boshlangich;
function tola(summa) {
if (summa > balans) {
return `Rad etildi: kartada ${balans} so'm bor`;
}
balans -= summa;
return `To'landi: ${summa} so'm`;
}
function qoldiq() {
return balans;
}
return { tola, qoldiq };
}
const karta = sovgaKartaYarat(100000);
console.log(karta.tola(35000));
console.log(karta.tola(90000));
console.log(karta.qoldiq());
console.log(karta.balans);Konsolda:
To'landi: 35000 so'm
Rad etildi: kartada 65000 so'm bor
65000
undefinedUch narsaga qarang:
- Qoida bitta joyda. "Ko'p yechib bo'lmaydi" faqat
tolaichida yozilgan. Balansni o'zgartirishning boshqa yo'li yo'q — demak qoidani aylanib o'tib ham bo'lmaydi. karta.balans—undefined. Qaytgan obyektda faqattolavaqoldiqbor (Shorthand —{ tola: tola, qoldiq: qoldiq }ning qisqasi).balanso'zgaruvchisi obyektda emas, ikki funksiyaning umumiy orqa cho'ntagida.- Har karta — o'z seyfi.
sovgaKartaYarat(50000)ni yana chaqirsangiz, yangi muhit, yangibalans(Closure darsidagi "har chaqiruv — yangi muhit").
Private holat (private state) — faqat o'z funksiyalari ko'radigan va o'zgartiradigan ma'lumot. Inglizcha "private" — "shaxsiy, begonaga yopiq". Tashqariga ochilgan funksiyalar esa ochiq qism (public API) deb ataladi: boshqa kod faqat shu "eshiklar" orqali ishlaydi.
Maslahat: Klasslarda private ma'lumot uchun maxsus
#sintaksisi bor — uni Private#maydonlar darsida o'rganamiz, hozir bilish shart emas. Closure esa eng eski va hamma joyda ishlaydigan usul.
2.3 Ichki obyektni tashqariga bermang
Private holat massiv yoki obyekt bo'lsa, bitta nozik joy bor:
function ovozlarYarat() {
const ovozlar = { osh: 0, manti: 0 };
return {
ber(taom) {
ovozlar[taom]++;
},
natija() {
return ovozlar;
},
};
}
const sorovnoma = ovozlarYarat();
sorovnoma.ber("osh");
sorovnoma.natija().osh = 1000;
console.log(sorovnoma.natija()); // { osh: 1000, manti: 0 }natija() seyfdagi obyektning o'zini qaytardi — havola bilan (Havola semantikasi). Tashqi kod shu havola orqali ichkariga kirib, ovozlarni buzdi. Seyf eshigini ochiq qoldirgandek.
Tuzatish — nusxa qaytarish: return { ...ovozlar }; (Spread). Endi tashqi kod nusxani o'zgartiradi, seyf esa butun qoladi. Qoida: private obyekt yoki massivni tashqariga nusxa bilan bering.
Tekshirib ko'ring:
sovgaKartaYaratdagiqoldiqfunksiyasini olib tashlab, o'rnigareturn { tola, balans };desak, nima o'zgaradi?
Javob
karta.balans endi mavjud, lekin u son nusxasi — obyekt yaratilgan paytdagi 100000. tola closure ichidagi balans ni kamaytiradi, obyektdagi balans esa o'zgarmaydi: tola(35000) dan keyin ham karta.balans — 100000. Primitiv qiymat obyektga ko'chirilganda bog'lanish uziladi. Joriy qiymatni berish uchun funksiya kerak — qoldiq(). Bu xatoni «Ko'p uchraydigan xatolar» bo'limida yana ko'ramiz.
3. Fabrika funksiya
3.1 Sozlamani bir marta berish
«Bahor» 7:00 dan 23:00 gacha ishlaydi, bron esa faqat 10:00 dan 22:00 gacha qabul qilinadi. Ikkala tekshiruv bir xil: "soat oraliq ichidami?". Ikki funksiyani qo'lda yozish o'rniga, ularni yasaydigan funksiya yozamiz:
function oraliqTekshiruvchiYarat(boshi, oxiri) {
return (soat) => soat >= boshi && soat < oxiri;
}
const ochiqmi = oraliqTekshiruvchiYarat(7, 23);
const bronQabulmi = oraliqTekshiruvchiYarat(10, 22);
console.log(ochiqmi(8)); // true
console.log(bronQabulmi(8)); // false
console.log(ochiqmi(23)); // falseFabrika funksiya (factory function) — sozlamani qabul qilib, shu sozlama bilan ishlaydigan yangi funksiya yoki obyekt qaytaradigan funksiya. Xuddi non zavodi: bir xil dastgoh, lekin xamir va shakl sozlamasi o'zgarsa — boshqa non chiqadi. oraliqTekshiruvchiYarat — dastgoh, (7, 23) — sozlama, ochiqmi — tayyor mahsulot.
Qaytgan funksiyalar predikat — true/false qaytaradi, shuning uchun nomlari "-mi" bilan tugaydi. Ular filter ga ham to'g'ridan-to'g'ri beriladi — buni quyidagi savolda tekshiring.
3.2 Fabrika obyekt ham qaytaradi
sovgaKartaYarat ham fabrika: u har chaqiruvda yangi obyekt yasaydi. Fabrika funksiyalar obyekt yasashning bir usuli. Keyinroq klasslar bilan ikkinchi usulni ko'ramiz va ikkalasini solishtiramiz. Hozircha asosiy farqni eslab qoling: fabrikada yashirin ma'lumot uchun closure yetarli, maxsus sintaksis kerak emas.
Tekshirib ko'ring:
[8, 9, 21].filter(bronQabulmi)nima qaytaradi? Bu yerdafiltercallback'ni uchta argument bilan chaqiradi — bu xavflimi?
Javob
[ 21 ] — faqat 21 soat 10 va 22 orasida. Xavfli emas: bronQabulmi faqat bitta parametr (soat) oladi, indeks va massiv e'tiborsiz qoladi (Callback imzosi). boshi va oxiri esa parametrdan emas, orqa cho'ntakdan keladi.
4. Bir martalik funksiya: once
4.1 Muammo: ikki marta to'lov
Mehmon "Buyurtma berish" tugmasini bosdi, internet sekin, hech narsa o'zgarmadi — u yana bosdi. Ikki buyurtma, ikki to'lov. Ba'zi ishlar faqat bir marta bajarilishi kerak: to'lov, ro'yxatdan o'tish, sahifani sozlash.
Closure bilan "allaqachon chaqirilganmi?" degan belgini saqlaymiz:
function birMartalik(fn) {
let chaqirildi = false;
let natija;
return (...argumentlar) => {
if (!chaqirildi) {
chaqirildi = true;
natija = fn(...argumentlar);
}
return natija;
};
}
const buyurtmaBer = birMartalik((summa) => {
console.log(`To'lov: ${summa} so'm`);
return "qabul qilindi";
});
console.log(buyurtmaBer(40000));
console.log(buyurtmaBer(40000));Konsolda:
To'lov: 40000 so'm
qabul qilindi
qabul qilindiTo'lov bir marta bo'ldi. Ikkinchi chaqiruv birinchisining natijasini qaytardi — chaqiruvchi kod "nimadir qaytmadi" deb sinib qolmaydi.
4.2 Qadamma-qadam
Keyingi ni bosib, chaqirildi va natija orqa cho'ntakda qanday o'zgarishini kuzating:
(...argumentlar) — rest parametri: nechta argument kelsa, hammasini massivga yig'adi. fn(...argumentlar) — spread: ularni yana alohida argumentlarga yoyib, asl funksiyaga uzatadi (Spread va rest). Shu juftlik tufayli birMartalik istalgan funksiyani — bir, ikki yoki besh parametrlisini — o'ray oladi.
4.3 { once: true } dan farqi
Listener opsiyalari darsida addEventListener(..., { once: true }) ni ko'rgansiz. U tinglovchini birinchi hodisadan keyin olib tashlaydi — faqat hodisalar uchun. birMartalik esa istalgan funksiyani o'raydi: tugma, taymer, server so'rovi, sozlash funksiyasi. Ikkalasi bir g'oya, har xil joyda.
5. Oddiy kesh: memoize
5.1 Bir xil savolga qayta-qayta hisob
Yetkazib berish narxini hisoblash og'ir bo'lsin deylik — xarita, tirbandlik, tarif. Bir xil masofa uchun javob har doim bir xil. Bitta sahifada esa u o'nlab marta so'raladi. Har safar qayta hisoblash — isrof.
Kesh (cache) — tayyor javoblarni saqlab qo'yadigan joy. Memoizatsiya (memoization) — funksiyaning har argumenti uchun javobni bir marta hisoblab, keyin keshdan berish. Inglizcha "memo" — "eslatma qog'ozi".
let hisoblashlar = 0;
function yetkazishNarxi(km) {
hisoblashlar++;
return 5000 + km * 2000;
}
function keshlangan(fn) {
const kesh = {};
return (arg) => {
if (!(arg in kesh)) {
kesh[arg] = fn(arg);
}
return kesh[arg];
};
}
const tezNarx = keshlangan(yetkazishNarxi);
console.log(tezNarx(3)); // 11000
console.log(tezNarx(3)); // 11000
console.log(tezNarx(7)); // 19000
console.log(tezNarx(3)); // 11000
console.log(hisoblashlar); // 2To'rt so'rov, lekin haqiqiy hisob — ikkita: 3 va 7 uchun bir martadan. kesh obyekti keshlangan ning muhitida, demak faqat qaytgan arrow uni ko'radi. arg in kesh — "shu kalit obyektda bormi?" (Obyekt bo'ylab yurish).
hisoblashlar sanog'i faqat ko'rsatish uchun — real kodda u bo'lmaydi.
5.2 Keshning uch sharti
Kesh tekin emas. Uni qo'yishdan oldin uchta savol bering:
- Funksiya toza (pure) mi? Kesh "bir xil argument — bir xil javob" degan ishonchga tayanadi (Pure funksiya). Javob vaqtga, tasodifga yoki tashqi holatga bog'liq bo'lsa — kesh eski javobni beraveradi.
- Argument bittami va primitivmi? Bizning oddiy
keshlanganfaqat bitta argumentni kalit qiladi. Obyekt kalitlari esa doim satr:3va"3"bitta kalit bo'lib qoladi, obyekt argument esa"[object Object]"ga aylanadi. Har qanday turdagi kalit uchun maxsus to'plam —Map— bor, uniMapdarsida o'rganamiz. - Kesh cheksiz o'sadimi? Har yangi argument keshga yoziladi va hech qachon o'chirilmaydi. Million xil argument — million yozuv xotirada. Bu closure'ning xotira tuzog'i, uni Closure tuzoqlari darsida ko'ramiz.
Tekshirib ko'ring:
const tasodifiy = keshlangan(() => Math.random());—tasodifiy(1)ni ikki marta chaqirsak, natijalar har xil bo'ladimi?
Javob
Yo'q — ikkalasi bir xil son. Birinchi chaqiruv tasodifiy sonni keshga yozdi, ikkinchisi keshdan oldi. Math.random toza emas (Math obyekti), shuning uchun uni keshlash xato: funksiya "tasodifiy" bo'lishdan to'xtadi. Keshni faqat toza funksiyalarga qo'ying.
6. Modul naqshi
6.1 IIFE + closure
Hozirgacha fabrikani ko'p marta chaqirdik: har karta — yangi seyf. Ba'zan esa butun dasturda bitta nusxa kerak: bitta savat, bitta sozlamalar. Buning uchun fabrikani yozib, darhol bir marta chaqirish kifoya — IIFE bilan:
const savat = (() => {
const mahsulotlar = [];
function qosh(nom, narx) {
mahsulotlar.push({ nom, narx });
}
function jami() {
return mahsulotlar.reduce((summa, m) => summa + m.narx, 0);
}
return { qosh, jami };
})();
savat.qosh("Osh", 35000);
savat.qosh("Ko'k choy", 5000);
console.log(savat.jami()); // 40000
console.log(savat.mahsulotlar); // undefinedArrow funksiya yaratildi va oxiridagi () bilan darhol chaqirildi. U obyekt qaytardi — savat ga shu obyekt tushdi. mahsulotlar massivi esa IIFE ning muhitida qoldi: qosh va jami uni ko'radi, tashqi kod — yo'q. jami ichida — reduce.
Modul naqshi (module pattern) — IIFE ichida yashirin ma'lumot va funksiyalar, tashqariga faqat bitta obyekt — ochiq qism. "Naqsh" (pattern) — ko'p uchraydigan muammoning tayyor, nomlangan yechimi.
6.2 Nega bu "tarixiy"?
2015-yilgacha JavaScript'da fayllarni bir-biridan ajratadigan rasmiy vosita yo'q edi. Sahifadagi barcha skriptlar bitta global muhitni bo'lishardi. Bir faylning jami funksiyasi boshqasinikini bosib ketardi. Modul naqshi har faylni "o'z seyfi"ga yopishning yagona usuli edi — jQuery (sahifa bilan ishlashni osonlashtiradigan eski, mashhur kutubxona) ham shunday yozilgan.
Bugun bu ishni ES modullar (import/export) qiladi: har fayl avtomatik o'z muhitiga ega. Ularni 10-qism oxirida, Nega modullar darsidan boshlab o'rganamiz. Modul naqshi esa eski kodda va bitta faylning ichida "kichik seyf" kerak bo'lganda hamon uchraydi. Global muhit qanday "to'lib ketishini" esa Global obyekt va globalThis darsida ko'ramiz.
7. debounce ichidan
7.1 Eslab olamiz
Fokus, scroll va resize darsida resize uchun debounce yozgan edik — "hodisalar to'xtab, jimlik bo'lgandagina bir marta bajar":
let taymer;
window.addEventListener("resize", () => {
clearTimeout(taymer);
taymer = setTimeout(() => {
console.log(`Yakuniy eni: ${window.innerWidth} px`);
}, 200);
});Endi bu kodni yangi ko'z bilan o'qiymiz. Tinglovchi arrow — closure: u taymer ni eslaydi. taymer tinglovchidan tashqarida — shuning uchun barcha resize hodisalari bitta taymer ni ko'radi va oldingi rejani bekor qila oladi. Agar let taymer tinglovchi ichida bo'lsa, har hodisa o'z yangi taymer ini olardi — bekor qiladigan narsa bo'lmasdi.
Lekin ikki noqulaylik bor. taymer — global, uni istalgan kod buzishi mumkin. Va ikkinchi debounce kerak bo'lsa (masalan, qidiruv maydoniga), taymer2 ni qo'lda e'lon qilish kerak. Yechim bugungi darsning o'zida: taymer ni seyfga — fabrikaga — qo'yamiz.
7.2 tinchlantir(fn, kutish)
function tinchlantir(fn, kutish) {
let taymer;
return (...argumentlar) => {
clearTimeout(taymer);
taymer = setTimeout(() => fn(...argumentlar), kutish);
};
}
const qidir = tinchlantir((soz) => {
console.log(`Qidiruv: ${soz}`);
}, 200);
// Foydalanuvchi tez yozyapti: har 50 ms da bitta harf
const harflar = ["o", "os", "osh"];
harflar.forEach((soz, i) => setTimeout(() => qidir(soz), i * 50));
// 600 ms dan keyin yana yozadi
const ikkinchi = ["l", "la", "lag'mon"];
ikkinchi.forEach((soz, i) => {
setTimeout(() => qidir(soz), 600 + i * 50);
});Konsolda:
Qidiruv: osh
Qidiruv: lag'monNode'da klaviatura yo'q, shuning uchun tez yozishni taymerlar bilan taqlid qildik. i * 50 — harflar 0, 50 va 100 ms da keladi; ikkinchi so'z esa 600, 650 va 700 ms da.
qidir olti marta chaqirildi, fn esa ikki marta — har "yozish to'lqini" oxirida. Kutubxonalarda bu funksiya debounce deb ataladi. Masalan, Lodash'da — _.debounce. Lodash — tayyor yordamchi funksiyalar kutubxonasi; uni hozir o'rnatish shart emas. Biz uni o'zbekcha tinchlantir deb atadik — 09-qismdagi "tinchlantirish" so'zidan.
Kodda uch xil closure ishlayapti:
taymer—tinchlantirchaqiruvining muhitida. Qaytgan arrow'ning hamma chaqiruvlari uni bo'lishadi. Har yangi chaqiruv eskisiniclearTimeoutbilan bekor qiladi.fnvakutish— parametrlar, ular ham shu muhitda. Sozlama bir marta berildi — fabrika.argumentlar— har chaqiruvning o'z muhitida.setTimeoutga berilgan() => fn(...argumentlar)uni eslab qoladi. 200 ms dan keyin u ishlaganda, aynan o'sha chaqiruvdagi so'z bilan ishlaydi — oxirgi so'z "osh" bilan.
Diagrammada birinchi to'lqin. Har yangi harf eski taymerni bekor qiladi, faqat oxirgisi "portlaydi":
sequenceDiagram
participant F as Foydalanuvchi
participant Q as qidir (closure)
participant T as taymer
F->>Q: "o" (0 ms)
Q->>T: 200 ms kut
F->>Q: "os" (50 ms)
Q->>T: eskisini bekor qil, yana 200 ms
F->>Q: "osh" (100 ms)
Q->>T: eskisini bekor qil, yana 200 ms
Note over T: 300 ms: jimlik
T->>Q: fn("osh") — bir marta7.3 Brauzerda: menyudan qidirish
Endi haqiqiy maydonda. Tez yozing — konsolga faqat to'xtaganingizdan 300 ms keyin bitta qator chiqadi:
<label for="qidiruv">Menyudan qidirish</label>
<input id="qidiruv" autocomplete="off">
<p id="javob" role="status">4 ta taom</p>
<script>
const taomlar = ["Osh", "Lag'mon", "Manti", "Ko'k choy"];
const maydon = document.getElementById("qidiruv");
const javob = document.getElementById("javob");
function tinchlantir(fn, kutish) {
let taymer;
return (...argumentlar) => {
clearTimeout(taymer);
taymer = setTimeout(() => fn(...argumentlar), kutish);
};
}
function qidir(soz) {
const kichik = soz.toLowerCase();
const topildi = taomlar.filter((t) =>
t.toLowerCase().includes(kichik),
);
javob.textContent = `${topildi.length} ta taom`;
console.log(`Qidiruv: "${soz}" — ${topildi.join(", ")}`);
}
const tinchQidir = tinchlantir(qidir, 300);
maydon.addEventListener("input", (event) => {
tinchQidir(event.target.value);
});
</script>"man" deb tez yozsangiz — bitta qator: Qidiruv: "man" — Manti. Harflar orasida to'xtab-to'xtab yozsangiz — har to'xtashda bittadan. tinchQidir bir marta, tinglovchidan tashqarida yasaldi. Bu juda muhim — nega ekanini «Ko'p uchraydigan xatolar» bo'limida ko'ramiz.
Debounce'ning egizagi — throttle ("ko'pi bilan har N ms da bir marta"). Ikkalasini, ularning sozlamalarini va qachon qaysi biri kerakligini Debounce va throttle darsida to'liq ko'ramiz.
Tekshirib ko'ring:
tinchlantirichidalet taymer;qatorini qaytgan arrow'ning ichiga ko'chirsak nima bo'ladi?
Javob
Debounce ishlamay qoladi: har chaqiruv o'z yangi taymer ini oladi (u undefined), clearTimeout(undefined) hech narsani bekor qilmaydi. "osh" misolida konsolga uch qator chiqadi: o, os, osh. Taymer barcha chaqiruvlar uchun umumiy bo'lishi kerak — shuning uchun u har chaqiruvda yangilanadigan muhitda emas, bir marta yaratiladigan muhitda turadi.
8. Ko'p uchraydigan xatolar
8.1 Holatni qiymat sifatida qaytarish
function hisoblagichYarat() {
let soni = 0;
return {
soni,
oshir() {
soni++;
},
};
}
const h = hisoblagichYarat();
h.oshir();
h.oshir();
console.log(h.soni); // 0{ soni } — soni: soni degani. Obyekt yaratilgan paytda soni — 0, va shu son obyektga ko'chirildi. oshir esa closure ichidagi o'zgaruvchini oshiradi — obyektdagi nusxa bilan aloqasi yo'q. Tuzatish: joriy qiymat uchun funksiya bering — qiymat() { return soni; }. Getter bilan ham bo'ladi — get soni() { return soni; } (Getter va setter): shunda h.soni har o'qilganda funksiya ishlaydi.
8.2 Debounce'ni har hodisada qayta yasash
maydon.addEventListener("input", (event) => {
tinchlantir(qidir, 300)(event.target.value);
});Har harf yangi tinchlantir chaqiradi — yangi muhit, yangi taymer. Oldingi taymerni hech kim bekor qilmaydi: "man" uchun uchta qidiruv. Bu Closure darsidagi "yasovchini har safar qayta chaqirish" xatosining o'zi. Tuzatish: const tinchQidir = tinchlantir(qidir, 300); — tinglovchidan tashqarida, bir marta.
8.3 Toza bo'lmagan funksiyani keshlash
«Keshning uch sharti» dagi Math.random misoli. Soat, sana, tashqi o'zgaruvchi yoki serverdagi ma'lumotga bog'liq funksiya keshlansa, u "muzlab" qoladi: kecha hisoblangan javobni bugun ham beradi. Xato xabari chiqmaydi — javob shunchaki eskirgan bo'ladi. Tuzatish: keshni faqat toza funksiyaga qo'ying.
8.4 once da natijani unutish
function birMartalik(fn) {
let chaqirildi = false;
return (...argumentlar) => {
if (!chaqirildi) {
chaqirildi = true;
return fn(...argumentlar);
}
};
}
const kursniOl = birMartalik(() => 12650);
console.log(kursniOl()); // 12650
console.log(kursniOl()); // undefinedIkkinchi chaqiruv undefined qaytardi — chaqiruvchi kod undefined * narx qilib, NaN oladi. Tuzatish: birinchi natijani o'zgaruvchiga saqlab, har safar uni qaytaring — «Bir martalik funksiya» bo'limidagi to'liq variant.
9. Mashqlar
1-mashq (oson): Navbat hisoblagichi obyekt bilan
navbatYarat() fabrikasini yozing. U { keyingi, hozirgi } obyektini qaytarsin: keyingi() navbatni bittaga oshirib, yangi raqamni qaytaradi, hozirgi() — oshirmasdan joriy raqamni. Raqamning o'zi tashqaridan ko'rinmasin.
Yechim
function navbatYarat() {
let raqam = 0;
return {
keyingi() {
raqam++;
return raqam;
},
hozirgi() {
return raqam;
},
};
}
const navbat = navbatYarat();
navbat.keyingi();
navbat.keyingi();
console.log(navbat.hozirgi()); // 2
console.log(navbat.raqam); // undefinedIkkala metod bitta muhitda tug'ilgan — bitta raqam ni bo'lishadi. Metodlar shorthand bilan yozildi.
2-mashq (o'rta): Aksiya — faqat 3 ta mehmonga
nechaMarta(fn, n) funksiyasini yozing: u fn ni ko'pi bilan n marta chaqiradigan funksiya qaytarsin. Limitdan keyin fn chaqirilmasin va "Aksiya tugadi" qaytsin. Bu — birMartalik ning umumlashgan shakli.
const sovgaBer = nechaMarta((ism) => `${ism}ga sovg'a choy`, 3);Yechim
function nechaMarta(fn, n) {
let qoldi = n;
return (...argumentlar) => {
if (qoldi === 0) {
return "Aksiya tugadi";
}
qoldi--;
return fn(...argumentlar);
};
}
const sovgaBer = nechaMarta((ism) => `${ism}ga sovg'a choy`, 3);
console.log(sovgaBer("Ali"));
console.log(sovgaBer("Malika"));
console.log(sovgaBer("Bobur"));
console.log(sovgaBer("Jasur"));Konsolda:
Aliga sovg'a choy
Malikaga sovg'a choy
Boburga sovg'a choy
Aksiya tugadiqoldi — orqa cho'ntakdagi hisoblagich. birMartalik(fn) endi nechaMarta(fn, 1) bilan deyarli bir xil — faqat u ikkinchi chaqiruvda birinchi natijani qaytaradi.
3-mashq (qiyin): Bekor qilinadigan debounce
tinchlantir ga imkoniyat qo'shing: qaytgan funksiyada bekorQil() metodi bo'lsin — u kutilayotgan chaqiruvni bekor qiladi. Masalan, mehmon qidiruv maydonini yopib qo'ydi — kutilayotgan qidiruv endi kerak emas.
Ishora: funksiya ham obyekt, unga xususiyat qo'shsa bo'ladi (IIFE va funksiya obyekt sifatida). bekorQil ham xuddi o'sha taymer ni ko'rishi kerak.
Yechim
function tinchlantir(fn, kutish) {
let taymer;
const tinch = (...argumentlar) => {
clearTimeout(taymer);
taymer = setTimeout(() => fn(...argumentlar), kutish);
};
tinch.bekorQil = () => clearTimeout(taymer);
return tinch;
}
const qidir = tinchlantir((soz) => {
console.log(`Qidiruv: ${soz}`);
}, 100);
qidir("osh");
qidir.bekorQil();
qidir("manti");Konsolda:
Qidiruv: mantitinch va bekorQil bitta muhitda tug'ildi — bitta taymer ni bo'lishadi. "osh" bekor qilindi, "manti" esa 100 ms dan keyin bajarildi. Lodash'dagi _.debounce ning .cancel() metodi aynan shunday ishlaydi.
4-mashq: Vazifalar qadami — id generatorini ulang
O'tgan darsda holat.keyingiId = 1 qatori ilovani qanday chalkashtirishini ko'rdingiz va idYasovchiYarat ni Node'da yozdingiz. Endi uni vazifalar.js ga ulang:
- Yangi branch:
git switch -c refactor/closure. holatdankeyingiIdni olib tashlang.- "1. Holat" bo'limiga
idYasovchiYaratva bitta generator —yangiId— qo'shing. vazifaQoshdagi id berish va oshirish qatorlariniyangiId()ga almashtiring.
Xulq o'zgarmasin: yangi vazifalar avvalgidek 4, 5, 6… id oladi, o'chirilgan id qayta berilmaydi.
Yechim
"1. Holat" bo'limi:
// 1. Holat — yagona haqiqat manbai
// (boshlangichVazifalar — avvalgidek, holat dan yuqorida)
const holat = {
vazifalar: boshlangichVazifalar,
filtr: "hammasi",
tahrirId: null,
// qoldi getter'i — avvalgidek
};
function idYasovchiYarat(boshlangich) {
let keyingi = boshlangich;
return () => keyingi++;
}
const yangiId = idYasovchiYarat(4);vazifaQosh — immutable yangilash darsidagi ko'rinishi, faqat id boshqa joydan keladi:
function vazifaQosh(matn) {
holat.vazifalar = [
...holat.vazifalar,
{ id: yangiId(), matn, bajarildi: false },
];
if (holat.filtr === "bajarilgan") {
holat.filtr = "hammasi";
}
render();
}Bir qator almashdi, bittasi ketdi: id: holat.keyingiId → id: yangiId(), holat.keyingiId++ esa butunlay yo'q — oshirishni generator o'zi qiladi.
Eski va yangi usul bir xil id ketma-ketligini berishini Node'da tekshiring — oradan o'chirish bo'lsa ham:
// Eski: id holatda
const eski = { keyingiId: 4 };
function eskiId() {
const id = eski.keyingiId;
eski.keyingiId++;
return id;
}
// Yangi: id closure'da
function idYasovchiYarat(boshlangich) {
let keyingi = boshlangich;
return () => keyingi++;
}
const yangiId = idYasovchiYarat(4);
// Qo'shish, qo'shish, (o'chirish — id'ga ta'siri yo'q), qo'shish
for (let i = 0; i < 3; i++) {
console.log(eskiId(), yangiId());
}Konsolda:
4 4
5 5
6 6Brauzerda butun ilovani sinab chiqing:
- Ikki vazifa qo'shing — ular 4 va 5 id oladi (DevTools → Elements,
lidagidata-id). - 4-ni o'chirib, yana qo'shing — yangisi 6 oladi, 4 qayta berilmaydi.
- Belgilash, tahrirlash (
Enter,Esc), uch filtr, takroriy matn xatosi, fokus — hammasi avvalgidek. - Console'da
holat.keyingiId = 1yozib, vazifa qo'shing. Endi uholatga ortiqcha xususiyat qo'shadi, xolos — yangi vazifa baribir to'g'ri id oladi.keyingini yozib ko'ring —ReferenceError: u global muhitda yo'q.
Commit: Vazifalar: id'larni closure bilan yasa. Branch'ni hali main ga qo'shmang — keyingi darsda kodni closure tuzoqlari bo'yicha tekshirib, keyin qo'shamiz.
Diqqat: Boshlang'ich
4— hozircha qo'lda yozilgan, chunki namuna vazifalar 1–3. 11-qismda vazifalar brauzer xotirasida saqlana boshlaydi (localStorage) va sahifa ochilganda boshqa vazifalar yuklanadi. Shuning uchun boshlang'ich son ro'yxatdagi eng katta id dan hisoblanishi kerak — buni OOP tamoyillari va kompozitsiya darsida, generatorni vazifalar ro'yxati klassiga ko'chirganda qilamiz.
10. Real ishda
- Kutubxonalar. Lodash'ning
_.once,_.memoize,_.debounce,_.throttlefunksiyalari — bugun yozganlaringizning to'liq, sinovdan o'tgan variantlari. Real loyihada ko'pincha shularni ishlatishadi — lekin ichida nima borligini endi bilasiz. - Qidiruv maydonlari. Internet do'kon, xarita, Telegram'dagi qidiruv — hammasi har harfda serverga so'rov yubormaydi, debounce bilan kutadi. Bu server yukini o'nlab marta kamaytiradi.
- To'lov tugmalari. Click/Payme kabi to'lov oynalarida "ikki marta bosish" himoyasi —
onceg'oyasi yoki "yuborilmoqda" holati (Dinamik UI'da qulaylik). - React.
useMemovauseCallbackhook'lari — memoizatsiya; komponentdagi hisoblagich esa closure ichidagi holat. 17-qismda ko'rasiz. - Intervyu. "Closure bilan private o'zgaruvchi yasang", "
onceni yozing", "debounceni yozing" — junior va middle intervyularining eng ko'p beriladigan amaliy topshiriqlari.
Xulosa
- Private holat: o'zgaruvchi closure'da, tashqariga faqat funksiyalar chiqadi. Qoida bitta joyda yoziladi va aylanib o'tib bo'lmaydi.
- Ichki obyekt yoki massivni tashqariga nusxa bilan bering; primitivni obyektga ko'chirsangiz, bog'lanish uziladi.
- Fabrika sozlamani bir marta oladi va tayyor funksiya yoki obyekt qaytaradi.
once— "chaqirildimi?" belgisi va birinchi natija; kesh — tayyor javoblar obyekti. Keshni faqat toza funksiyaga qo'ying.- Modul naqshi — IIFE + closure; bugun bu ishni ES modullar qiladi.
debounce: taymer barcha chaqiruvlar uchun umumiy muhitda, argumentlar har chaqiruvning o'zida. Uni bir marta yasang.
Keyingi dars: Closure tuzoqlari — var li sikl, eskirgan closure va xotira ushlab qolish: closure'ning uch klassik muammosini va ularning yechimini ko'ramiz.
Manbalar
- MDN: "Closures" — "Emulating private methods with closures" bo'limi — developer.mozilla.org
- Lodash hujjatlari:
_.once,_.memoize,_.debounce— lodash.com/docs - MDN: "setTimeout()", "clearTimeout()" — developer.mozilla.org
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!