Mundarija (33)
- Bu darsda
- 1. Nega bu kerak?
- 2. Naqshlar oilasi
- 3. Kuzatuvchi (Observer)
- 3.1 Muammo
- 3.2 Eslab olamiz: EventTarget
- 3.3 O'z kuzatuvchimiz
- 3.4 O'zimiznikimi yoki EventTarget?
- 3.5 Qachon kerak emas
- 4. Modul (Module)
- 4.1 Yopiq holat, ochiq API
- 4.2 Qachon kerak emas
- 5. Yaratuvchi naqshlar: Factory va Singleton
- 5.1 Factory
- 5.2 Singleton
- 6. Strategiya (Strategy)
- 7. Tuzilmaviy naqshlar: Dekorator va Adapter
- 7.1 Dekorator: funksiyani o'rash
- 7.2 Adapter: begona shaklni moslash
- 8. Naqshni ko'r-ko'rona qo'llamang
- 9. Ko'p uchraydigan xatolar
- 9.1 Obunani bekor qilishni unutish
- 9.2 emit paytida ro'yxatni o'zgartirish
- 9.3 Strategiya kaliti xatosi
- 9.4 Singleton — "hamma narsa uchun quti"
- 10. Mashqlar
- 1-mashq (oson): Yetkazib berish strategiyasi
- 2-mashq (o'rta): Chaqiruvlarni sanaydigan dekorator
- 3-mashq (qiyin): once bilan kuzatuvchi
- 4-mashq: Vazifalar qadami — render kuzatuvchi orqali, branch'ni yakunlash
- 11. Real ishda
- Xulosa
- Manbalar
JavaScript dizayn naqshlari: Kuzatuvchi, Modul, Factory, Singleton va boshqalar
Qisqacha: Dizayn naqshi — qayta-qayta uchraydigan muammoning nom berilgan yechimi. JavaScript'da eng ko'p kerak bo'ladiganlari: kuzatuvchi (o'zgarish haqida obunachilarga xabar), modul (yopiq holat + ochiq API), factory (yaratish bir joyda) va singleton (yagona nusxa). Yana uchtasi: strategiya (algoritmni almashtirish), dekorator (funksiyani o'rash) va adapter (begona shaklni moslash). Ko'pi JS'da oddiy funksiya va obyekt bilan yoziladi. Har naqshning "kerak emas" holati ham bor — muammo bo'lmasa, naqsh ham kerak emas.
Bu darsda
- Dizayn naqshi nima ekanini va ularning uch oilasini tushuntira olasiz.
- O'z kuzatuvchi (EventEmitter) klassingizni yozib, uni
EventTargetbilan solishtira olasiz. - Modul, factory va singleton naqshlarini JavaScript'ning o'z vositalari bilan yoza olasiz.
- Strategiya, dekorator va adapter bilan
ifzanjirlari va takroriy kodni yo'qota olasiz. - Har naqsh uchun "bu yerda kerak emas" deya oladigan bo'lasiz.
vazifalardarenderni kuzatuvchi orqali chaqirasiz va klasslar branch'ini yakunlaysiz.
Oldin bilishingiz kerak: class ichidan, OOP tamoyillari va kompozitsiya, Closure amaliyotda, Custom events va EventTarget.
1. Nega bu kerak?
Rustam aka yangi oshpazga "piyozni qovurib, keyin go'shtni sol" deb har safar tushuntirmaydi. "Zirvak qil" deydi — va oshxonada hamma tushunadi. "Zirvak" — ko'p qadamli ishning nomi. Nom bo'lgani uchun ular tez gaplashadi va bir xil narsani tushunadi.
Dasturchilarda ham shunday so'zlar bor. "Bu yerga kuzatuvchi qo'yamiz", "adapter yozib qo'y" — jamoada bu gaplar o'n qatorlik tushuntirishning o'rnini bosadi. Bunday nomli yechimlar dizayn naqshlari (design patterns) deyiladi: ko'p loyihada qayta-qayta uchraydigan muammoning sinovdan o'tgan yechim qolipi. Naqsh — tayyor kod emas. U "shu muammoga shunday tuzilma yaxshi ishlaydi" degan g'oya.
Siz bu naqshlarning yarmini allaqachon ishlatgansiz, faqat nomini bilmagansiz. Taom.dan() — factory. markaz.addEventListener — kuzatuvchi. Closure bilan yashirilgan hisoblagich — modul. Bugun ularga nom beramiz va qachon kerak, qachon kerak emasligini ko'ramiz.
2. Naqshlar oilasi
1994-yilda to'rt muallif — Gamma, Helm, Johnson va Vlissides — "Design Patterns" kitobini chiqardi. Ularni "To'rtlik" (Gang of Four, GoF) deb atashadi. Kitobda 23 naqsh uch oilaga bo'lingan:
flowchart TB
N["Dizayn naqshlari"]
Y["Yaratuvchi<br/>obyekt qanday yaratiladi"]
T["Tuzilmaviy<br/>obyektlar qanday ulanadi"]
X["Xulqiy<br/>obyektlar qanday gaplashadi"]
N --> Y
N --> T
N --> X
Y --> Y1["Factory, Singleton"]
T --> T1["Dekorator, Adapter, Modul"]
X --> X1["Kuzatuvchi, Strategiya"]Diagrammadagi pastki qatorlar — bu darsda ko'radiganlarimiz. Modul GoF kitobida yo'q — u JavaScript'ning o'z naqshi, lekin vazifasiga ko'ra tuzilmaviy oilaga yaqin.
Bitta muhim farq. GoF kitobi Java va C++ kabi tillar uchun yozilgan — u yerda funksiyani qiymat sifatida uzatish qiyin edi. Shuning uchun ko'p naqsh klass va interfeys ierarxiyasi bo'lib chiqqan. JavaScript'da funksiya — oddiy qiymat (Callback va HOF). Natijada GoF'dagi uch klassli naqsh JS'da ko'pincha bitta funksiya yoki bitta obyekt bo'ladi. Bugungi misollar shuni ko'rsatadi.
3. Kuzatuvchi (Observer)
3.1 Muammo
«Bahor» buyurtmalar tizimi. Yangi buyurtma kelganda uch ish bo'lishi kerak: oshxona ekraniga chiqsin, kassa yozib qo'ysin, ofitsiantga xabar ketsin. Eng to'g'ri yo'l:
function buyurtmaQosh(buyurtma) {
royxat.push(buyurtma);
oshxonaEkraniniYangila(buyurtma);
kassagaYoz(buyurtma);
ofitsiantgaXabar(buyurtma);
}Ishlaydi. Lekin buyurtmaQosh uchala qismni ham biladi. To'rtinchisi — Jasur akaning telefoniga hisobot — qo'shilsa, bu funksiya yana o'zgaradi. Oshxona ekrani olib tashlansa — yana. Buyurtma qo'shish mantiqi unga aloqasi yo'q narsalar sababli o'zgarib turadi.
Kuzatuvchi (observer) naqshi buni teskari qiladi: buyurtmalar ro'yxati kimga xabar berishini bilmaydi. Qiziqqan qismlar o'zlari "obuna" bo'ladi. Ro'yxat faqat "o'zgarish bo'ldi" deb e'lon qiladi. Hayotda — Telegram kanal: kanal egasi obunachilarni tanimaydi, post yozadi va hammasi oladi.
3.2 Eslab olamiz: EventTarget
Custom events va EventTarget darsida buni brauzerning tayyor vositasi bilan qilgan edik — event bus. Meros darsida esa class Savat extends EventTarget ni ko'rdik. Bu — kuzatuvchi naqshining brauzerdagi o'rnatilgan shakli: addEventListener — obuna, dispatchEvent — e'lon.
3.3 O'z kuzatuvchimiz
Naqsh ichidan qanday ishlashini tushunish uchun uni o'zimiz yozamiz. Node'dagi o'xshash klass nomi bilan uni ko'pincha EventEmitter ("hodisa chiqaruvchi") deyishadi:
class Hodisalar {
#tinglovchilar = {};
on(nom, tinglovchi) {
this.#tinglovchilar[nom] ??= [];
this.#tinglovchilar[nom].push(tinglovchi);
return () => this.off(nom, tinglovchi);
}
off(nom, tinglovchi) {
const royxat = this.#tinglovchilar[nom] ?? [];
this.#tinglovchilar[nom] = royxat.filter(
(t) => t !== tinglovchi,
);
}
emit(nom, malumot) {
const royxat = this.#tinglovchilar[nom] ?? [];
royxat.forEach((tinglovchi) => tinglovchi(malumot));
}
}
const buyurtmalar = new Hodisalar();
buyurtmalar.on("yangi", (b) => console.log(`Oshxona: ${b.taom}`));
const kassaniUz = buyurtmalar.on("yangi", (b) => {
console.log(`Kassa: +${b.narx}`);
});
buyurtmalar.emit("yangi", { taom: "Osh", narx: 35000 });
kassaniUz();
buyurtmalar.emit("yangi", { taom: "Manti", narx: 30000 });Konsolda:
Oshxona: Osh
Kassa: +35000
Oshxona: MantiUch metod — butun naqsh:
on(nom, tinglovchi)— obuna.??=(Mantiqiy tayinlash) shu nomdagi ro'yxat hali yo'q bo'lsa, bo'sh massiv yaratadi. Metod obunani bekor qiladigan funksiya qaytaradi — u closure:nomvatinglovchini eslab qoladi (Closure).off— tinglovchini olib tashlaydi.filtereski massivni o'zgartirmaydi, yangisini yasaydi. Bu ataylab — «Ko'p uchraydigan xatolar» da sababini ko'ramiz.emit(nom, malumot)— e'lon: shu nomdagi har tinglovchini chaqiradi.
Ikkinchi emit da kassa jim qoldi — kassaniUz() obunani bekor qildi. Qadamma-qadam ko'ring — Keyingi tugmasini bosib, tinglovchilar ro'yxati qanday o'zgarishiga qarang:
3.4 O'zimiznikimi yoki EventTarget?
EventTarget |
O'z Hodisalar |
|
|---|---|---|
| Qayerda | Brauzer va Node'da tayyor | O'zingiz yozasiz (20 qator) |
| Ma'lumot | CustomEvent ning detail ida |
Istalgan argument to'g'ridan-to'g'ri |
| Qo'shimchalar | once, signal, DOM bilan bir xil API |
Faqat yozganingiz |
| Kimga | DOM bilan ishlaydigan kod | Kichik, DOM'siz model |
Ikkalasi ham bir xil naqsh. Brauzerda tayyor EventTarget ko'pincha yetarli. O'zinikini yozish naqshni tushunish va DOM'siz kichik obyekt uchun foydali — «Vazifalar qadami» da shuni qilamiz.
3.5 Qachon kerak emas
Custom events darsidagi qoida o'zgarmaydi: bitta joy bitta ishni chaqirsa — oddiy funksiya chaqiruvi. Kuzatuvchi faqat bitta hodisaga bir nechta, bir-birini bilmaydigan qism javob berganda o'zini oqlaydi.
Narxi — kuzatish qiyinligi: "bu son nega o'zgardi?" degan savolga javob endi emit dan qaytib, hamma on larni qidirishni talab qiladi. Va unutilgan obunalar — xotirani band qilib, keraksiz ishni bajarib yotadi.
4. Modul (Module)
4.1 Yopiq holat, ochiq API
Bu naqshni Closure amaliyotda darsida nomi bilan ko'rgan edingiz — endi uni boshqa naqshlar qatoriga qo'yamiz. Qisqa eslatma, «Bahor» kassasi misolida:
const kassa = (() => {
let tushum = 0; // tashqaridan ko'rinmaydi
const cheklar = [];
function chekUr(summa) {
tushum += summa;
cheklar.push(summa);
}
function hisobot() {
return `${cheklar.length} ta chek, ${tushum} so'm`;
}
return { chekUr, hisobot };
})();
kassa.chekUr(35000);
kassa.chekUr(5000);
console.log(kassa.hisobot()); // 2 ta chek, 40000 so'm
console.log(kassa.tushum); // undefinedModul naqshi — IIFE ichida yopiq holat va funksiyalar, tashqariga esa faqat return qilingan obyekt — ochiq API. tushum ni hech kim to'g'ridan-to'g'ri o'zgartira olmaydi.
Bu naqsh yillar davomida JavaScript'da asosiy bo'lgan, chunki tilda modullar yo'q edi va hamma skriptlar bitta global muhitni bo'lishardi (Global obyekt). IIFE har kutubxonaga o'z "xonasini" berardi.
4.2 Qachon kerak emas
Bugun JavaScript'da haqiqiy modullar bor — export va import. Har fayl o'z-o'zidan alohida xona bo'ladi va IIFE o'rash kerak emas. Ularni Nega modullar va ESM export darsida o'rganamiz — hozir bilish shart emas. Yangi kodda modul naqshi o'rniga ESM yoziladi. IIFE shaklini esa eski kutubxonalarda va <script> bilan ulanadigan kodda uchratasiz.
Klasslar bilan ham xuddi shu natija: # maydonlar — yopiq holat, metodlar — ochiq API. Bir nechta nusxa kerak bo'lsa — klass; faqat bitta bo'lsa — modul (yoki keyingi naqsh).
5. Yaratuvchi naqshlar: Factory va Singleton
5.1 Factory
Getter/setter va static darsida Taom.dan(malumot) ni yozdik — bu factory metod. Naqshning to'liq shakli — qaysi klassni yaratishni ham factory hal qiladi:
class Taom {
constructor(nom, narx) {
this.nom = nom;
this.narx = narx;
}
}
class Ichimlik extends Taom {
constructor(nom, narx, hajm) {
super(nom, narx);
this.hajm = hajm;
}
}
function menyuElementi(malumot) {
if (malumot.tur === "ichimlik") {
return new Ichimlik(malumot.nom, malumot.narx, malumot.hajm);
}
return new Taom(malumot.nom, malumot.narx);
}
const menyu = [
{ tur: "taom", nom: "Osh", narx: 35000 },
{ tur: "ichimlik", nom: "Ko'k choy", narx: 5000, hajm: 0.5 },
].map(menyuElementi);
const klasslar = menyu.map((m) => m.constructor.name);
console.log(klasslar); // [ 'Taom', 'Ichimlik' ]Chaqiruvchi faqat ma'lumot beradi va tayyor obyekt oladi. Qaysi klass, qaysi argumentlar — factory ichida. Yangi tur (Shirinlik) qo'shilsa, faqat menyuElementi o'zgaradi. .map(menyuElementi) — oddiy funksiya, ichida this yo'q, shuning uchun qavssiz berish xavfsiz (Callback imzosi tuzog'i ham yo'q: ikkinchi parametr yo'q).
Qachon kerak emas: bitta klass va ma'lumot doim bir xil shaklda bo'lsa — oddiy new Taom(...). Har klassga "zavod" yasash — ortiqcha qatlam.
5.2 Singleton
Singleton — butun dasturda bitta nusxasi bo'lishi kerak bo'lgan obyekt. «Bahor» sozlamalari: ish vaqti, valyuta, manzil. Ikki xil sozlama bo'lsa — biri 7:00 da, biri 8:00 da ochiladi. Bitta bo'lishi shart.
Klasslarda singleton shunday yoziladi:
class Sozlamalar {
static #yagona = null;
static olish() {
Sozlamalar.#yagona ??= new Sozlamalar();
return Sozlamalar.#yagona;
}
ochilish = "7:00";
yopilish = "23:00";
}
const a = Sozlamalar.olish();
const b = Sozlamalar.olish();
console.log(a === b); // truestatic #yagona — yagona nusxa uchun joy. Birinchi olish() uni yaratadi, keyingilari o'shani qaytaradi.
JavaScript'da esa bu ko'pincha ortiqcha. Bitta obyekt kerak bo'lsa — shunchaki bitta obyekt yarating:
const sozlamalar = Object.freeze({
ochilish: "7:00",
yopilish: "23:00",
valyuta: "so'm",
});
console.log(sozlamalar.yopilish); // 23:00Obyekt literal bir marta yaratiladi — u allaqachon yagona. Object.freeze uni o'zgarmas qiladi. ESM modullari bilan esa bu yanada aniq: moduldan eksport qilingan obyekt butun dastur uchun bitta bo'ladi.
Qachon kerak emas (deyarli har doim): singleton — yashirin global o'zgaruvchi. Har qanday kod uni olib, o'zgartirishi mumkin, va keyin "kim o'zgartirdi?" degan savolga javob topish qiyin. Testlashda ham muammo: har test bitta umumiy holatni bo'lishadi. Ko'p jamoalar singletonni "anti-naqsh" — zararli naqsh — deb ham ataydi. Faqat haqiqatan yagona resurs (sozlamalar, bitta ulanish) uchun va iloji bo'lsa o'zgarmas holda ishlating.
Tekshirib ko'ring: Hamkasbingiz
Savatklassini singleton qilmoqchi: "saytda savat bitta-ku". To'g'rimi?
Javob
Ehtiyot bo'lish kerak. Bugun savat bitta, lekin ertaga "saqlangan savatlar" yoki ikki foydalanuvchi bitta qurilmada paydo bo'lishi mumkin. Sinovda ham har test toza savat bilan boshlashi kerak — singleton buni qiyinlashtiradi. Yaxshiroq: oddiy klass, ilova boshida bitta nusxa yaratib, kerakli joylarga uzatish. "Bitta nusxa" — ilovaning qarori bo'lsin, klassning majburiyati emas.
6. Strategiya (Strategy)
Chegirma hisoblash. «Bahor» da uch xil mijoz: oddiy, talaba (10%) va tushlik paytidagi aksiya (5 000 so'm). Birinchi yozuv:
function yakuniyNarx(summa, tur) {
if (tur === "talaba") {
return summa * 0.9;
} else if (tur === "tushlik") {
return summa - 5000;
}
return summa;
}Har yangi chegirma — yangi else if. Strategiya (strategy) naqshi har algoritmni alohida, almashtiriladigan bo'lakka ajratadi. JavaScript'da bo'lak — oddiy funksiya, to'plam — obyekt:
const chegirmalar = {
oddiy: (summa) => summa,
talaba: (summa) => summa * 0.9,
tushlik: (summa) => Math.max(summa - 5000, 0),
};
function yakuniyNarx(summa, tur) {
const strategiya = chegirmalar[tur] ?? chegirmalar.oddiy;
return strategiya(summa);
}
console.log(yakuniyNarx(40000, "talaba")); // 36000
console.log(yakuniyNarx(40000, "tushlik")); // 35000
console.log(yakuniyNarx(40000, "noma'lum")); // 40000yakuniyNarx endi chegirmalarni bilmaydi — u faqat "kerakli strategiyani topib chaqir" deydi. Yangi chegirma — chegirmalar ga yangi qator, yakuniyNarx o'zgarmaydi. ?? chegirmalar.oddiy — noma'lum tur uchun xavfsiz zaxira.
Siz buni avval ham ko'rgansiz: toSorted(Taom.solishtir) — saralash algoritmi bitta, taqqoslash strategiyasi esa tashqaridan beriladi (sort). Har HOF metodga beriladigan callback — kichik strategiya.
Qachon kerak emas: ikki variant va ular kam o'zgarsa — oddiy if yoki ternary tushunarliroq. Strategiya ko'p (uch va undan ortiq) va tez-tez qo'shiladigan variantlar uchun.
7. Tuzilmaviy naqshlar: Dekorator va Adapter
7.1 Dekorator: funksiyani o'rash
Dekorator (decorator) — mavjud funksiyani o'zgartirmasdan unga yangi xulq qo'shadigan o'ram. JavaScript'da u — funksiya qabul qilib, yangi funksiya qaytaradigan HOF:
function logBilan(fn) {
return (...argumentlar) => {
const natija = fn(...argumentlar);
const royxat = argumentlar.join(", ");
console.log(`${fn.name}(${royxat}) → ${natija}`);
return natija;
};
}
function chekSummasi(narx, soni) {
return narx * soni;
}
const kuzatilgan = logBilan(chekSummasi);
const jami = kuzatilgan(35000, 2) + kuzatilgan(5000, 3);
console.log(jami); // 85000Konsolda:
chekSummasi(35000, 2) → 70000
chekSummasi(5000, 3) → 15000
85000chekSummasi ga tegmadik. logBilan uni o'radi: chaqiruvdan oldin va keyin qo'shimcha ish qiladi, natijani esa o'zgarishsiz qaytaradi. O'ram asl funksiya bilan bir xil imzoda — uni asl funksiya o'rniga hamma joyda ishlatish mumkin. Closure amaliyotda darsidagi birMartalik (once) ham — dekorator.
TypeScript va zamonaviy JavaScript'da klass metodlari uchun @dekorator sintaksisi ham bor (Standart dekoratorlar) — g'oya o'sha, faqat yozuvi boshqa. Hozir bilish shart emas.
Qachon kerak emas: qo'shimcha xulq bitta joyda kerak bo'lsa — uni shu joyga yozing. Bir funksiyani besh qavat dekorator bilan o'rash xatoni topishni qiyinlashtiradi: stack trace'da asl funksiya o'rniga o'ramlar ko'rinadi.
7.2 Adapter: begona shaklni moslash
Jasur aka yetkazib berish xizmatiga ulandi. Xizmat buyurtmalarni o'z shaklida yuboradi:
const xizmatdan = [
{ title: "Plov", price_uzs: 35000, qty: 2 },
{ title: "Manti", price_uzs: 30000, qty: 1 },
];
function buyurtmagaAylantir(x) {
return { nom: x.title, narx: x.price_uzs, soni: x.qty };
}
const buyurtmalar = xizmatdan.map(buyurtmagaAylantir);
const jami = buyurtmalar.reduce((s, b) => s + b.narx * b.soni, 0);
console.log(buyurtmalar[0]); // { nom: 'Plov', narx: 35000, soni: 2 }
console.log(jami); // 100000«Bahor» kodi nom, narx, soni ni kutadi. Xizmat esa title, price_uzs, qty beradi. Adapter (adapter) — ikki mos kelmaydigan shaklni ulaydigan kichik qatlam: telefon zaryadlovchisining vilkasi uchun adapter kabi. Hamma begona ma'lumot bitta funksiyadan o'tadi; qolgan kod faqat o'z shaklini ko'radi. Xizmat ertaga price_uzs ni price ga o'zgartirsa — faqat adapter tuzatiladi.
Getter/setter va static darsidagi Taom.dan() va 11-qismda serverdan keladigan JSON'ni o'z obyektlaringizga aylantirish — adapter va factory birgalikda.
Qachon kerak emas: ikkala tomon ham sizniki bo'lsa — adapter yozmang, shakllardan birini to'g'rilang. Adapter — o'zgartira olmaydigan begona kod bilan ishlash uchun.
8. Naqshni ko'r-ko'rona qo'llamang
Naqshlarni o'rgangan dasturchi ko'pincha har joyda ularni ko'ra boshlaydi. Ikki chaqiruv uchun event bus, bitta klass uchun factory, uchta if uchun strategiya. Natija — kod uzayadi, tushunish qiyinlashadi, muammo esa yo'q edi.
Uch savol bilan tekshiring:
- Muammo hozir bormi? "Keyinroq kerak bo'lishi mumkin" — sabab emas. Dasturchilarda buning nomi bor: YAGNI — "You Aren't Gonna Need It", "bu sizga kerak bo'lmaydi".
- Naqshsiz kod qanchalik yomon? Uchta
if— normal. O'n beshta, har hafta yangisi — strategiya vaqti. - Jamoa tushunadimi? Naqsh — umumiy til. Hamkasblar bilmasa, foydasi yarim.
| Naqsh | Muammo | JS'dagi oddiy shakli | Kerak emas, agar |
|---|---|---|---|
| Kuzatuvchi | Bir hodisaga ko'p qism javob beradi | EventTarget yoki on/emit |
Bitta tinglovchi |
| Modul | Yopiq holat, ochiq API | ESM fayl (eskisi — IIFE) | — (ESM bor) |
| Factory | Yaratish mantig'i murakkab | Funksiya yoki static dan |
Bitta klass, bitta shakl |
| Singleton | Yagona resurs | Bitta obyekt + freeze |
Deyarli doim |
| Strategiya | Almashtiriladigan algoritmlar | Funksiyalar obyekti | 2 ta variant |
| Dekorator | Funksiyaga xulq qo'shish | HOF o'ram | Bitta joyda kerak |
| Adapter | Begona shakl | Aylantiruvchi funksiya | Ikkala tomon sizniki |
Jadvalni chop etib, kompyuteringiz yoniga ilib qo'yish shart emas. Undan muhimi — har satrning oxirgi ustuni: naqsh muammo bo'lgandagina kerak.
9. Ko'p uchraydigan xatolar
9.1 Obunani bekor qilishni unutish
Sahifadan olib tashlangan qism on bilan obuna bo'lib qolsa, har emit da u ishlayveradi — ko'rinmas, lekin xotira va vaqt sarflab. Ba'zan esa allaqachon o'chirilgan elementga yozmoqchi bo'lib xato beradi. Tuzatish: on qaytargan funksiyani saqlang va qism yo'qolganda chaqiring.
9.2 emit paytida ro'yxatni o'zgartirish
const tinglovchilar = [];
function bir() {
console.log("bir");
tinglovchilar.splice(tinglovchilar.indexOf(bir), 1);
}
const ikki = () => console.log("ikki");
tinglovchilar.push(bir, ikki);
tinglovchilar.forEach((t) => t()); // faqat "bir"bir birinchi chaqiruvda o'zini splice bilan olib tashladi. Massiv siljidi: ikki 0-o'ringa o'tdi, forEach esa 1-o'ringa o'tib ketdi — ikki chaqirilmadi (Callback imzosi darsidagi "aylanayotgan massivni o'zgartirish" tuzog'i). Shuning uchun Hodisalar.off eski massivni o'zgartirmay, filter bilan yangisini yasaydi: emit dagi forEach eski massivni oxirigacha aylanadi.
9.3 Strategiya kaliti xatosi
Kalitda xato bo'lsa — chegirmalar["tlaba"] — yoki tur noma'lum bo'lsa, natija undefined. Uni chaqirish esa TypeError: strategiya is not a function beradi. Tuzatish: zaxira bering (?? chegirmalar.oddiy) yoki kalitlarni bitta joyda o'zgarmas sifatida saqlang.
9.4 Singleton — "hamma narsa uchun quti"
Sozlamalar ichiga avval ish vaqti, keyin joriy foydalanuvchi, keyin savat, keyin tanlangan til qo'shiladi... Bir yildan keyin u dasturning hamma qismi bog'liq bo'lgan ulkan global obyektga aylanadi. Tuzatish: singletonda faqat haqiqatan umumiy va kam o'zgaradigan narsa; qolgani — o'z obyektlarida.
10. Mashqlar
1-mashq (oson): Yetkazib berish strategiyasi
«Bahor» yetkazib berish narxi: piyoda olib ketish — 0, shahar ichida — 10 000, shahar tashqarisida — har km uchun 2 000 so'm. yetkazishNarxi(tur, km) ni strategiya bilan yozing. Ishora: strategiya funksiyalari ikkinchi argument km ni olishi mumkin.
Yechim
const yetkazishlar = {
ozi: () => 0,
shahar: () => 10000,
tashqari: (km) => km * 2000,
};
function yetkazishNarxi(tur, km = 0) {
const strategiya = yetkazishlar[tur] ?? yetkazishlar.shahar;
return strategiya(km);
}
console.log(yetkazishNarxi("ozi")); // 0
console.log(yetkazishNarxi("tashqari", 12)); // 24000Har strategiya kerakli argumentni oladi, keraksizini e'tiborsiz qoldiradi. Zaxira sifatida shahar tanlandi — noma'lum tur uchun eng xavfsiz narx.
2-mashq (o'rta): Chaqiruvlarni sanaydigan dekorator
sanabBor(fn) dekoratorini yozing: o'ralgan funksiya natijani o'zgarishsiz qaytarsin va har chaqiruvda konsolga "narxQosh: 2 ta chaqiruv" kabi nechanchi marta chaqirilganini yozsin. Funksiya nomi — fn.name. Ishora: hisoblagich — closure ichidagi let.
Yechim
function sanabBor(fn) {
let soni = 0;
return (...argumentlar) => {
soni++;
console.log(`${fn.name}: ${soni} ta chaqiruv`);
return fn(...argumentlar);
};
}
const qosh = sanabBor(function narxQosh(a, b) {
return a + b;
});
qosh(35000, 5000);
console.log(qosh(28000, 5000));Konsolda:
narxQosh: 1 ta chaqiruv
narxQosh: 2 ta chaqiruv
33000Dekorator nomi ishini aytadi: "sanab bor". Har o'ralgan funksiyaning o'z soni si bor: closure har sanabBor chaqiruvida yangi muhit yaratadi.
3-mashq (qiyin): once bilan kuzatuvchi
«Kuzatuvchi» bo'limidagi Hodisalar klassiga once(nom, tinglovchi) metodini qo'shing: tinglovchi faqat birinchi emit da ishlasin, keyin o'zi olib tashlansin. Ishora: on ga asl tinglovchini emas, uni o'raydigan funksiyani bering — u avval obunani bekor qilsin, keyin asl tinglovchini chaqirsin.
Yechim
class Hodisalar {
#tinglovchilar = {};
on(nom, tinglovchi) {
this.#tinglovchilar[nom] ??= [];
this.#tinglovchilar[nom].push(tinglovchi);
return () => this.off(nom, tinglovchi);
}
off(nom, tinglovchi) {
const royxat = this.#tinglovchilar[nom] ?? [];
this.#tinglovchilar[nom] = royxat.filter(
(t) => t !== tinglovchi,
);
}
once(nom, tinglovchi) {
const bekorQil = this.on(nom, (malumot) => {
bekorQil();
tinglovchi(malumot);
});
return bekorQil;
}
emit(nom, malumot) {
const royxat = this.#tinglovchilar[nom] ?? [];
royxat.forEach((tinglovchi) => tinglovchi(malumot));
}
}
const savat = new Hodisalar();
savat.once("qoshildi", (t) => {
console.log(`Aksiya: ${t} bilan choy bepul`);
});
savat.on("qoshildi", (t) => console.log(`Savatga: ${t}`));
savat.emit("qoshildi", "Osh");
savat.emit("qoshildi", "Manti");Konsolda:
Aksiya: Osh bilan choy bepul
Savatga: Osh
Savatga: MantiBu — dekorator va kuzatuvchi birgalikda: once asl tinglovchini o'radi. O'ram emit paytida bekorQil() ni chaqiradi — ro'yxat filter bilan yangilangani uchun ikkinchi tinglovchi ("Savatga") o'tkazib yuborilmadi. «emit paytida ro'yxatni o'zgartirish» bo'limidagi tuzoqdan himoya amalda ishladi. Natija Custom events darsidagi { once: true } aksiyasi bilan bir xil.
4-mashq: Vazifalar qadami — render kuzatuvchi orqali, branch'ni yakunlash
refactor/vazifalar-royxati branch'idagi oxirgi ish. Hozir har o'zgartiruvchi funksiya oxirida render() ni o'zi chaqiradi:
function vazifaniAlmashtir(id) {
holat.vazifalar.almashtir(id);
render();
}vazifaQosh, vazifaniOchir, matnniAlmashtir da ham xuddi shunday. Royxat o'zgardi — chaqiruvchi chizishni eslab qolishi kerak. Yangi o'zgartiruvchi qo'shilsa va render() unutilsa — ekran eskirib qoladi.
Vazifa: VazifalarRoyxati ni kuzatuvchi qiling.
#tinglovchilarvaobuna(tinglovchi)— obunani bekor qiluvchi funksiya qaytarsin.- Private
#xabarBer()— hamma tinglovchini chaqirsin;qosh,almashtir,matnniOzgartir,ochiroxirida chaqirilsin. - Fayl oxirida, birinchi
render()dan oldin:holat.vazifalar.obuna(render). - To'rt funksiyadan
render()ni olib tashlang. Filtr va tahrir holatini o'zgartiradigan funksiyalar (filtrniTanla,tahrirniBoshla,tahrirniYop) esarender()ni o'zi chaqirishda davom etadi — ular royxatni o'zgartirmaydi.
Xulq o'zgarmasin. Diqqat: ikki funksiyada qatorlar tartibi muhim bo'lib qoladi.
Yechim
VazifalarRoyxati ga:
#tinglovchilar = [];
obuna(tinglovchi) {
this.#tinglovchilar.push(tinglovchi);
return () => {
this.#tinglovchilar = this.#tinglovchilar.filter(
(t) => t !== tinglovchi,
);
};
}
#xabarBer() {
this.#tinglovchilar.forEach((tinglovchi) => tinglovchi());
}O'zgartiruvchi metodlarning oxiriga — this.#xabarBer(); (to'rttasiga ham). Masalan:
almashtir(id) {
this.top(id).almashtir();
this.#xabarBer();
}Qiymat qaytaradigan ikki metodda #xabarBer() — return dan oldin: return dan keyin yozilgan qator hech qachon bajarilmaydi. qosh da — return vazifa dan oldin, matnniOzgartir da esa javobni avval o'zgaruvchiga olamiz:
matnniOzgartir(id, matn) {
const ozgardi = this.top(id).matnniOzgartir(matn);
this.#xabarBer();
return ozgardi;
}vazifalar.js dagi funksiyalar:
function vazifaQosh(matn) {
if (holat.filtr === "bajarilgan") {
holat.filtr = "hammasi";
}
holat.vazifalar.qosh(matn);
}
function vazifaniAlmashtir(id) {
holat.vazifalar.almashtir(id);
}
function vazifaniOchir(id) {
holat.vazifalar.ochir(id);
}
function matnniAlmashtir(id, matn) {
holat.tahrirId = null;
holat.vazifalar.matnniOzgartir(id, matn);
}Fayl oxiri:
holat.vazifalar.obuna(render);
render();Tartib nega muhim. Endi render royxat o'zgargan zahoti, qosh yoki matnniOzgartir ichida ishlaydi. Undan keyingi qatorlar ekranga yetib bormaydi. Eski tartibda (avval royxat, keyin holat) ikki xato chiqadi. Ishonmasangiz — qatorlarni almashtirib, o'zingiz sinab ko'ring:
vazifaQoshda filtr royxatdan keyin o'zgarsa: «Bajarilgan» filtrida qo'shilgan vazifa ko'rinmaydi, «Bajarilgan» tugmasi bosilgan holda qoladi,holat.filtresa allaqachon"hammasi". Ekran va holat bir-biriga zid.matnniAlmashtirdatahrirId = nullroyxatdan keyin bo'lsa: saqlashdan keyin qator tahrir rejimida qolib ketadi, fokus esa «Tahrirlash» o'rniga "Ro'yxat" sarlavhasiga tushadi.
Qoida: kuzatuvchi bilan avval tinglovchiga kerak bo'lgan hamma holatni tayyorlang, oxirida e'lon qiling. Bu e'lon sinxron: render qaytgandan keyingina fokusla(...) ishlaydi — 09-qismdagi "avval render, keyin fokus" tartibi saqlandi.
Endi render qayerdan chaqiriladi:
| Nima o'zgardi | Kim chaqiradi |
|---|---|
| Vazifalar (qo'shish, belgilash, tahrir, o'chirish) | royxat, kuzatuvchi orqali |
| Filtr, tahrir rejimi | o'sha funksiya, to'g'ridan-to'g'ri |
| Sahifa ochilishi | fayl oxiridagi render() |
Yangi o'zgartiruvchi metod (masalan, keyinchalik "hammasini tozalash") #xabarBer() ni chaqirsa bas — ekran o'zi yangilanadi. obuna qaytargan funksiyani hozir saqlamadik: render sahifa yopilguncha kerak. Ikkinchi tinglovchi (masalan, 11-qismda saqlash) qo'shilganda u ham royxat ni o'zgartirmaydi — faqat obuna bo'ladi.
Brauzerda butun ilovani sinab chiqing — bu klasslar modulining yakuniy tekshiruvi:
- Ochilganda — uch vazifa, "2 ta vazifa qoldi, 1 ta bajarildi"; belgilash va o'chirishda hisob darhol yangilanadi.
- Belgilash, «Faol» filtrida belgilash (fokus qo'shni katakka, oxirgisida — sarlavhaga), o'chirish — avvalgidek.
- «Bajarilgan» filtrida yangi vazifa qo'shing — filtr «Hammasi» ga o'tadi, vazifa ko'rinadi.
- Tahrirlang va
Enter— qator oddiy holatga qaytadi, fokus «Tahrirlash» da, "O'zgarish saqlandi.".Esc— bekor. - Bo'sh matn va takror matn — xato xabarlari avvalgidek.
- Eksport — uch vazifa,
toJSONorqali, avvalgidek.
Bu besh darsdagi o'zgarishlar:
| Dars | vazifalar.js da |
|---|---|
Getter/setter va static |
Vazifa.MAKS_UZUNLIK, Vazifa.dan |
| Meros | — (mashqlar dagi tajriba: meros yaramadi) |
| OOP tamoyillari | VazifalarRoyxati — kompozitsiya, static dan/mosmi, #yangiId |
class ichidan |
royxatga toJSON — eksport tiklandi, saqlashga tayyor |
| shu dars | obuna + #xabarBer — render kuzatuvchi orqali |
Commit va branch'ni yakunlash:
git add assets/js/vazifalar.js
git commit -m "Render: ro'yxat o'zgarishini kuzatuvchi orqali chiz"
git switch main
git merge refactor/vazifalar-royxati
git branch -d refactor/vazifalar-royxati
git pushPages bir-ikki daqiqada yangilanadi (Yakuniy loyiha). Telefonda oching: foydalanuvchi hech qanday farq ko'rmasligi kerak — ichkarida esa ilova endi klasslar va kuzatuvchi ustida turibdi. Yakuniy loyihada (vazifalar v2) bu tuzilma modullarga bo'linadi.
11. Real ishda
- Holat kutubxonalari. Redux, Zustand va boshqa holat kutubxonalari (18-qism) ichida — kuzatuvchi:
subscribe(listener)obunani bekor qiluvchi funksiya qaytaradi, aynan bugungiobunakabi. - Node.js.
EventEmitter— Node'ning eng asosiy klasslaridan biri (24-qism); server, fayl oqimlari, WebSocket — hammasion/emit. BugungiHodisalar— uning kichik nusxasi. - Backend freymvorklar. NestJS (30-qism) dekoratorlar (
@Controller,@Get) va factory'lar ustiga qurilgan. Tashqi to'lov tizimlari (Click, Payme) bilan ishlashda har birining javobini o'z shaklingizga keltiradigan adapter yoziladi (29-qism). - Intervyu. "Qaysi dizayn naqshlarini bilasiz va ishlatgansiz?", "Observer va Pub/Sub farqi?", "Singleton nega ko'pincha yomon?" — o'rta darajadagi tez-tez beriladigan savollar. Pub/Sub — kuzatuvchining vositachi orqali ishlaydigan turi: e'lon qiluvchi va obunachi bir-birini umuman ko'rmaydi. Yaxshi javob — naqsh nomi + o'z loyihangizdan misol + qachon ishlatmaslik. Bugungi
vazifalarqadami tayyor misol.
Xulosa
- Dizayn naqshi — qayta uchraydigan muammoning nomli yechimi; GoF bo'yicha uch oila: yaratuvchi, tuzilmaviy, xulqiy.
- Kuzatuvchi:
on(obuna, bekor qiluvchi funksiya qaytaradi) +emit; brauzerda tayyori —EventTarget. Bitta tinglovchi uchun kerak emas. - Modul — yopiq holat + ochiq API (IIFE va closure); bugun uning o'rnini ESM egallagan.
- Factory — yaratishni bir joyga yig'adi; singleton — JS'da ko'pincha bitta muzlatilgan obyekt, aksariyat holatda kerak emas.
- Strategiya — funksiyalar obyekti
ifzanjiri o'rniga; dekorator — HOF o'ram; adapter — begona shaklni o'zimiznikiga moslash. - Naqsh — muammo bo'lganda. YAGNI: "keyin kerak bo'lishi mumkin" — sabab emas.
Keyingi dars: Map — istalgan turdagi kalitli, tartibni saqlaydigan lug'at: oddiy obyektdan qachon yaxshiroq ekanini ko'ramiz.
Manbalar
- E. Gamma, R. Helm, R. Johnson, J. Vlissides, "Design Patterns: Elements of Reusable Object-Oriented Software" (1994)
- MDN: "EventTarget", "Closures", "IIFE" — developer.mozilla.org
- Node.js hujjatlari: "Events" (
EventEmitter) — nodejs.org
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!