Mundarija (36)
- Bu darsda
- 1. Nega bu kerak?
- 2. Birinchi o'z hodisamiz: new Event va dispatchEvent
- 2.1 Yaratish va yuborish
- 2.2 dispatchEvent sinxron
- 2.3 isTrusted: hodisani kim yaratdi?
- 3. CustomEvent va detail: hodisa bilan ma'lumot
- 3.1 Ma'lumot uzatish
- 3.2 Ikki tuzoq
- 4. bubbles va cancelable
- 4.1 O'z hodisangiz standart holatda bubble qilmaydi
- 4.2 cancelable va preventDefault: "ruxsat so'rash"
- 4.3 composed — kelajak uchun bir gap
- 4.4 Brauzer hodisasi va o'z hodisangiz: farqlar jadvali
- 5. Nomlash qoidalari
- 6. EventTarget: elementsiz hodisa markazi
- 6.1 Har kim hodisa "chala" oladi
- 6.2 Node'da ham ishlaydi
- 6.3 Event bus: qachon kerak, qachon yo'q
- 6.4 Bir martalik va to'xtatiladigan tinglovchilar
- 6.5 Klass bilan — oldindan qisqa ko'rinish
- 7. DevTools'da o'z hodisalaringiz
- 8. Ko'p uchraydigan xatolar
- 8.1 bubbles: true ni unutish
- 8.2 Ma'lumot detail dan tashqarida
- 8.3 Tinglovchi hodisadan keyin qo'yildi
- 8.4 Nomdagi xato
- 8.5 Mantiqni soxta click ga bog'lash
- 9. Mashqlar
- 1-mashq (oson): Yangi bron
- 2-mashq (o'rta): Savat hisoblagichi
- 3-mashq (qiyin): Bronni bekor qilish — ruxsat bilan
- 4-mashq: Portfolio qadami — tema o'zgarganini e'lon qilish
- 10. Real ishda
- Xulosa
- Manbalar
Custom events JavaScript'da: CustomEvent, dispatchEvent va EventTarget
Qisqacha: Brauzer hodisalarini tinglashdan tashqari, o'z hodisangizni ham yaratib, elementga "chalib" yubora olasiz:
new CustomEvent(...)vadispatchEvent(...). Ma'lumotdetailichida keladi; ota elementlar eshitishi uchunbubbles: truekerak. Barcha tinglovchilardispatchEventichida, shu zahoti ishlaydi,new EventTarget()esa sahifada ko'rinmaydigan "hodisa markazi" beradi.
Bu darsda
new Eventvanew CustomEventbilan o'z hodisangizni yaratib,dispatchEventbilan yubora olasiz.detailorqali ma'lumot uzatasiz,bubblesvacancelablenima qilishini bilasiz.dispatchEventsinxron ekanini va qaytaradigan qiymatini tushunasiz.EventTargetbilan "event bus" yasaysiz va qachon kerak emasligini ham bilasiz.- Portfolio'dagi tema almashinuvini hodisa orqali e'lon qilasiz.
Oldin bilishingiz kerak: Event delegation, Hodisa tarqalishi: capture va bubble, Event obyekti va standart harakatlar, Fokus, scroll va resize hodisalari.
1. Nega bu kerak?
«Bahor» saytiga savat qo'shildi. Mehmon "Savatga" tugmasini bossa, uch narsa o'zgarishi kerak: sarlavhadagi savat belgisidagi son, sahifa pastidagi jami summa va ekran chetida "Osh qo'shildi" degan qisqa xabar.
Aziz birinchi bo'lib shunday yozdi:
function savatgaQosh(taom) {
sonniYangila();
jaminiHisobla();
xabarniKorsat(taom);
}Ishlaydi. Lekin bir hafta o'tib Nodira "savatga qo'shilganda analitikaga ham yozilsin" dedi. Yana bir hafta o'tib — "tugma bir lahza yashil bo'lsin". Har safar savatgaQosh ichiga qo'l tiqish kerak. Bu funksiya savatdan boshqa hamma narsani biladigan bo'lib qoldi.
Choyxonaning oshxonasini eslang. Oshpaz Rustam aka osh tayyor bo'lganda har ofitsiantning oldiga borib aytmaydi. U qo'ng'iroqni chaladi. Kimga kerak bo'lsa — Otabek, kassir, Jasur aka — o'zi eshitib, o'z ishini qiladi. Ertaga yangi ofitsiant kelsa, Rustam aka hech narsani o'zgartirmaydi: yangi odam shunchaki qo'ng'iroqni tinglaydi.
O'z hodisangiz — aynan shu qo'ng'iroq. Savat "savatga qo'shildi" deb e'lon qiladi, kerakli qismlar o'zlari tinglaydi.
2. Birinchi o'z hodisamiz: new Event va dispatchEvent
2.1 Yaratish va yuborish
Ikki qadam kerak:
new Event("nom")— hodisa obyektini yaratish.element.dispatchEvent(hodisa)— uni shu elementga "yuborish" (chalish).
Tinglash esa odatdagidek — addEventListener("nom", ...). Brauzer hodisasidan hech qanday farqi yo'q:
<p id="oshxona">Oshxona</p>
<script>
const oshxona = document.getElementById("oshxona");
oshxona.addEventListener("osh-tayyor", (event) => {
console.log(`2. Eshitildi: ${event.type}`);
});
console.log("1. Qo'ng'iroq chalinmoqda");
oshxona.dispatchEvent(new Event("osh-tayyor"));
console.log("3. dispatchEvent tugadi");
</script>Konsolda:
1. Qo'ng'iroq chalinmoqda
2. Eshitildi: osh-tayyor
3. dispatchEvent tugadi2.2 dispatchEvent sinxron
Tartibga qarang: 2 raqamli qator 3 dan oldin chiqdi. Demak, dispatchEvent tinglovchini darhol chaqirdi va u ishlab bo'lgandan keyingina keyingi qatorga o'tdi.
Bunday ishlash sinxron (synchronous) deyiladi — "navbat bilan, shu zahoti": keyingi qator oldingi ish tugashini kutadi. Bu so'zni Callback darsida "darhol chaqiriladigan callback" sifatida ko'rgansiz.
Bu brauzer hodisalaridan muhim farq. Mehmon tugmani bosganda brauzer hodisani "keyinroq", kodingiz bo'shaganda yuboradi. dispatchEvent esa oddiy funksiya chaqiruviga o'xshaydi: barcha tinglovchilar navbat bilan, shu zahoti bajariladi.
Bundan ikki xulosa:
- Tinglovchi hodisadan oldin qo'yilgan bo'lishi kerak. Keyin qo'yilsa — u o'tib ketgan qo'ng'iroqni eshitmaydi.
- Tinglovchi ichida xato bo'lsa, u konsolda qizil chiqadi. Lekin
dispatchEventni chaqirgan kod to'xtamaydi va keyingi tinglovchilar ham ishlaydi.
Oxirgi gapni sinab ko'ramiz. Birinchi tinglovchi mavjud bo'lmagan stolni qidiradi va tanish xatoga uchraydi:
<p id="oshxona">Oshxona</p>
<script>
const oshxona = document.getElementById("oshxona");
oshxona.addEventListener("osh-tayyor", () => {
const stol = document.querySelector("#stol-5");
console.log(stol.textContent);
});
oshxona.addEventListener("osh-tayyor", () => {
console.log("2. Kassa eshitdi");
});
console.log("1. Yuborilmoqda");
const natija = oshxona.dispatchEvent(new Event("osh-tayyor"));
console.log("3. Davom etdi:", natija);
</script>Konsolda:
1. Yuborilmoqda
Uncaught TypeError: Cannot read properties of null (reading 'textContent')
2. Kassa eshitdi
3. Davom etdi: trueTarjimasi: "null ning xususiyatini o'qib bo'lmaydi ('textContent' ni o'qiyotganda)" — #stol-5 topilmadi. Xato birinchi tinglovchini to'xtatdi, xolos. Ikkinchi tinglovchi ham, dispatchEvent dan keyingi qator ham ishladi. Oddiy funksiya chaqiruvida bunday emas: chaqirilgan funksiya ichida xato bo'lsa, uni chaqirgan kod ham to'xtaydi.
Bu xulq yaxshi ham, xavfli ham. Yaxshi — bitta buzuq tinglovchi butun saytni yiqitmaydi. Xavfli — xatoni sezmay qolish oson: sahifa "ishlayotgandek" ko'rinadi. Shuning uchun o'z hodisalaringiz bilan ishlaganda konsoldagi qizil qatorlarga e'tibor bering.
2.3 isTrusted: hodisani kim yaratdi?
Har hodisada event.isTrusted bor (Event obyekti darsida ko'rgansiz). Mehmon harakatidan kelgan hodisada — true, kod yaratganida — false. Shu sababli dispatchEvent bilan "soxta bosish" yaratsangiz ham, brauzer uni haqiqiy deb hisoblamaydi. Masalan, faqat haqiqiy bosishdan keyin ruxsat beriladigan narsalar (yangi oyna ochish, to'liq ekran) soxta hodisada ishlamaydi.
new Event dan tashqari brauzer hodisalarining o'z "konstruktorlari" ham bor. Konstruktor — new bilan chaqirilib, yangi obyekt yasaydigan funksiya: new MouseEvent("click"), new KeyboardEvent("keydown", { key: "a" }). Ular bilan brauzer hodisasini taqlid qilish mumkin — lekin faqat tinglovchilar uchun. Soxta klaviatura hodisasi maydonga harf yozmaydi:
<label for="ism">Ism</label>
<input id="ism">
<script>
const ism = document.getElementById("ism");
ism.addEventListener("keydown", (event) => {
console.log("keydown:", event.key, event.isTrusted);
});
ism.dispatchEvent(new KeyboardEvent("keydown", { key: "a" }));
console.log("Qiymat:", JSON.stringify(ism.value));
</script>Konsolda:
keydown: a false
Qiymat: ""Tinglovchi hodisani oldi (isTrusted — false), maydon esa bo'sh qoldi. Brauzer harf yozishni faqat haqiqiy tugma bosilganda qiladi. Bu xavfsizlik uchun muhim: begona skript sizning nomingizdan parol maydoniga yoza olmaydi. Qoida: o'z hodisangiz uchun Event va CustomEvent yetarli. Brauzer hodisalarini taqlid qilish asosan avtomatik testlarda kerak bo'ladi — ularni kursda alohida o'rganamiz.
Tekshirib ko'ring: Quyidagi kodda konsolga nima chiqadi?
oshxona.dispatchEvent(new Event("choy-tayyor"));— keyinoshxona.addEventListener("choy-tayyor", () => console.log("Choy!"));
Javob
Hech narsa. dispatchEvent sinxron: hodisa yuborilgan paytda hali tinglovchi yo'q edi. Tinglovchi keyin qo'yildi, lekin qo'ng'iroq allaqachon chalinib bo'lgan. Brauzer o'tgan hodisalarni eslab qolmaydi.
3. CustomEvent va detail: hodisa bilan ma'lumot
3.1 Ma'lumot uzatish
"Savatga qo'shildi" degan xabar yetarli emas — nima qo'shildi? Ma'lumot uzatish uchun CustomEvent bor. U Event ning o'zi, faqat qo'shimcha detail xususiyati bilan:
<button type="button" id="osh">Osh — 35 000</button>
<script>
const osh = document.getElementById("osh");
osh.addEventListener("savat:qoshildi", (event) => {
const { nom, narx } = event.detail;
console.log(`Savatga: ${nom}, ${narx} so'm`);
console.log(event.detail);
console.log(event, event.isTrusted);
});
const hodisa = new CustomEvent("savat:qoshildi", {
detail: { nom: "Osh", narx: 35000 },
});
osh.dispatchEvent(hodisa);
</script>Konsolda:
Savatga: Osh, 35000 so'm
{nom: 'Osh', narx: 35000}
CustomEvent {type: 'savat:qoshildi'} falsedetail ga istalgan qiymat sig'adi: son, satr, massiv, obyekt. Odatda — obyekt: keyinchalik yangi maydon qo'shish oson bo'ladi. Tinglovchida uni destructuring bilan ochish qulay.
3.2 Ikki tuzoq
detail faqat detail kaliti orqali uzatiladi. Ma'lumotni to'g'ridan-to'g'ri ikkinchi argumentga yozsangiz, u yo'qoladi:
<p id="savat">Savat</p>
<script>
const savat = document.getElementById("savat");
savat.addEventListener("savat:qoshildi", (event) => {
console.log(event.detail, event.nom);
});
savat.dispatchEvent(
new CustomEvent("savat:qoshildi", { nom: "Osh" }),
);
</script>Konsolda:
null undefinedXato xabari yo'q — ma'lumot shunchaki jimgina tashlab yuborildi. detail berilmasa, uning qiymati null. Tuzatish: { detail: { nom: "Osh" } }.
Ikkinchi tuzoq — havola semantikasi. detail dagi obyekt nusxalanmaydi: barcha tinglovchilar bitta obyektni oladi. Bitta tinglovchi event.detail.narx = 0 qilsa, keyingisi ham 0 ni ko'radi. Qoida: tinglovchida detail ni faqat o'qing, o'zgartirmang.
4. bubbles va cancelable
4.1 O'z hodisangiz standart holatda bubble qilmaydi
Brauzerning click hodisasi bubble qiladi. new Event va new CustomEvent bilan yaratilgani esa yo'q — buni alohida so'rash kerak. Ko'p boshlovchilar shu yerda qoqiladi:
<ul id="menyu">
<li id="osh">Osh</li>
</ul>
<script>
const menyu = document.getElementById("menyu");
const osh = document.getElementById("osh");
menyu.addEventListener("tanlandi", (event) => {
console.log(`ul eshitdi: ${event.target.id}`);
});
osh.dispatchEvent(new CustomEvent("tanlandi"));
console.log("— birinchisi (bubbles yo'q) yuborildi");
osh.dispatchEvent(new CustomEvent("tanlandi", { bubbles: true }));
console.log("— ikkinchisi (bubbles: true) yuborildi");
</script>Konsolda:
— birinchisi (bubbles yo'q) yuborildi
ul eshitdi: osh
— ikkinchisi (bubbles: true) yuborildiBirinchi hodisa li da qoldi — ul uni eshitmadi. Ikkinchisi bubbles: true bilan yuborildi va bubble qilib ul ga yetdi.
Qachon bubbles: true kerak? Hodisa yuqoridagi elementlarda tinglanishi kerak bo'lsa — deyarli doim. Ayniqsa delegation bilan: kartalar 200 ta bo'lsa ham, document dagi bitta tinglovchi hammasini eshitadi.
4.2 cancelable va preventDefault: "ruxsat so'rash"
dispatchEvent qiymat qaytaradi: true yoki false. Agar hodisa cancelable: true bilan yaratilgan bo'lsa va tinglovchilardan biri event.preventDefault() chaqirsa — false qaytadi.
Bu "ruxsat so'rash" imkonini beradi. Savat "Manti'ni o'chiraman" deb e'lon qiladi. Oshxona tinglovchisi "manti allaqachon qozonda — o'chirib bo'lmaydi" deb preventDefault qiladi. Savat qaytgan false ni ko'rib, o'chirmaydi:
<ul id="savat">
<li data-nom="Osh">Osh
<button type="button">O'chirish</button></li>
<li data-nom="Manti" data-holat="pishmoqda">Manti (pishmoqda)
<button type="button">O'chirish</button></li>
</ul>
<script>
const savat = document.getElementById("savat");
// Savat: o'chirishdan oldin "ruxsat so'raydi"
savat.addEventListener("click", (event) => {
const qator = event.target.closest("li");
if (!event.target.matches("button") || qator === null) {
return;
}
const hodisa = new CustomEvent("savat:ochirish", {
detail: { nom: qator.dataset.nom },
bubbles: true,
cancelable: true,
});
const ruxsat = qator.dispatchEvent(hodisa);
if (ruxsat) {
qator.remove();
console.log(`O'chirildi: ${qator.dataset.nom}`);
}
});
// Oshxona: pishayotgan taomni o'chirishga yo'l qo'ymaydi
document.addEventListener("savat:ochirish", (event) => {
if (event.target.dataset.holat === "pishmoqda") {
event.preventDefault();
console.log(`Mumkin emas: ${event.detail.nom} pishmoqda`);
}
});
</script>Manti yonidagi "O'chirish" ni bosing: konsolda Mumkin emas: Manti pishmoqda, qator joyida qoladi. Osh yonidagisini bosing: O'chirildi: Osh, qator yo'qoladi (tekshirdik).
Savat oshxona haqida hech narsa bilmaydi. U faqat "kimdir qarshi emasmi?" deb so'raydi. Ertaga "chegirmali taomni o'chirib bo'lmaydi" degan qoida kerak bo'lsa — yangi tinglovchi qo'shiladi, savat kodi o'zgarmaydi.
Diqqat: cancelable siz preventDefault hech narsa qilmaydi va dispatchEvent baribir true qaytaradi. "Ruxsat so'rash" uchun ikkalasi ham kerak.
4.3 composed — kelajak uchun bir gap
CustomEvent ning yana bir opsiyasi bor: composed: true. U hodisani Shadow DOM chegarasidan o'tkazadi. Shadow DOM — komponentning ichki, tashqaridan yashirin DOM'i; u Web Components darsida keladi. Hozir bilish shart emas: oddiy sahifada composed hech narsani o'zgartirmaydi.
4.4 Brauzer hodisasi va o'z hodisangiz: farqlar jadvali
| Belgi | Brauzer hodisasi (click) |
O'z hodisangiz |
|---|---|---|
| Kim yaratadi | Brauzer, mehmon harakatidan | Sizning kodingiz |
isTrusted |
true |
false |
| Qachon tinglovchiga keladi | Keyinroq, kod bo'shaganda | Darhol, dispatchEvent ichida |
bubbles |
Hodisaga qarab (click — ha, focus — yo'q) |
Standart — yo'q |
| Ma'lumot | Tayyor xususiyatlar (key, clientX) |
detail |
Qolgan hamma narsa bir xil: addEventListener, removeEventListener, once, signal, capture va bubble, stopPropagation, target va currentTarget. O'z hodisangiz — DOM hodisalar tizimining to'la huquqli a'zosi.
O'zingiz to'ldiring (opsiya nomini yozing):
- Taom nomi va narxini hodisa bilan uzatish uchun kalit —
- Hodisa
lidanulvadocumentga ko'tarilishi uchun — - Tinglovchidagi
preventDefaultishlashi uchun —
Tekshirib ko'ring: Aziz
new CustomEvent("savat:qoshildi", { detail })ni tugmada yubordi vadocumentda tingladi. Hech narsa chiqmadi. Nima yetishmaydi?
Javob
bubbles: true. O'z hodisasi standart holatda bubble qilmaydi va tugmadan document gacha ko'tarilmaydi. Yoki hodisani to'g'ridan-to'g'ri document ga yuborish kerak: document.dispatchEvent(...).
5. Nomlash qoidalari
Brauzer hodisa nomiga hech qanday cheklov qo'ymaydi — istalgan satr ishlaydi. Shuning uchun tartibni o'zimiz saqlaymiz:
- Kichik harf. Brauzer hodisalari shunday:
click,keydown.SavatQoshildiemas. - Prefiks bilan:
savat:qoshildi,savat:ochirish,menyu:yopildi. Ikki nuqtadan oldingi qism — kim e'lon qilyapti. Bir qarashda tushunarli va brauzer hodisalari bilan hech qachon to'qnashmaydi. - Brauzer nomlarini olmang. O'z
clickyokichangehodisangizni yaratsangiz, haqiqiy va soxta hodisalar aralashib ketadi. - Nomni bir joyda saqlang. Nomdagi xato jim o'tadi:
savat:qoshildini yuborib,savat:qoshlidini tinglasangiz — xato yo'q, natija ham yo'q. Katta loyihada nomlar bitta o'zgarmasga yoziladi:const SAVAT_QOSHILDI = "savat:qoshildi";.
Yana bir farq: brauzer hodisalarining onclick kabi xususiyatlari bor (addEventListener asoslari). O'z hodisangizda bunday xususiyat yo'q — faqat addEventListener.
6. EventTarget: elementsiz hodisa markazi
6.1 Har kim hodisa "chala" oladi
DOM daraxti darsidagi diagrammani eslang: tugunlar oilasining eng tepasida EventTarget turardi. addEventListener, removeEventListener va dispatchEvent aynan shu oiladan keladi. Shuning uchun ular document, har bir element va window da bor.
EventTarget ni o'zingiz ham yaratishingiz mumkin — new EventTarget(). Bu DOM elementi emas, sahifada ko'rinmaydi. U faqat hodisa yuborish va tinglash uchun "markaz":
<p>Buyurtmalar markazi (konsolga qarang)</p>
<script>
const markaz = new EventTarget();
markaz.addEventListener("buyurtma", (event) => {
console.log(`Oshxona: ${event.detail.taom} tayyorlansin`);
});
markaz.addEventListener("buyurtma", (event) => {
console.log(`Kassa: +${event.detail.narx} so'm`);
});
markaz.dispatchEvent(new CustomEvent("buyurtma", {
detail: { taom: "Lag'mon", narx: 28000 },
}));
console.log(markaz);
</script>Konsolda:
Oshxona: Lag'mon tayyorlansin
Kassa: +28000 so'm
EventTarget {}Bitta e'lon — ikki tinglovchi. Ular bir-birini bilmaydi va qo'shilish tartibida chaqiriladi.
6.2 Node'da ham ishlaydi
EventTarget, Event va CustomEvent — brauzer asboblari, lekin Brauzer muhiti darsida aytganimizdek, foydali asboblarni Node ham o'ziga qo'shib olgan. Shu kodni terminalda ham ishga tushirsa bo'ladi — "Ishga tushir" tugmasini bosing:
const markaz = new EventTarget();
markaz.addEventListener("buyurtma", (event) => {
console.log(`Oshxona: ${event.detail.taom}`);
});
markaz.dispatchEvent(new CustomEvent("buyurtma", {
detail: { taom: "Manti" },
}));
console.log("Yuborildi");Konsolda:
Oshxona: Manti
YuborildiNode'da bu sinxronlik yana bir bor ko'rinadi: Oshxona qatori Yuborildi dan oldin.
6.3 Event bus: qachon kerak, qachon yo'q
Hamma qismlar bitta markaz orqali gaplashadigan tuzilmani event bus ("hodisalar shinasi") deyishadi.
flowchart LR
T["Savatga tugmasi"] -->|savat:qoshildi| M(("markaz"))
M --> S["Savat belgisi: son"]
M --> J["Jami summa"]
M --> X["Qisqa xabar"]
M --> A["Analitika"]Diagrammada chapdagi tugma o'ngdagi to'rt qismning birortasini ham bilmaydi. Beshinchi qism kerak bo'lsa — yangi strelka qo'shiladi, tugma kodi o'zgarmaydi. Bu bog'liqliksiz aloqa deyiladi: qismlar faqat hodisa nomi orqali tanish.
Lekin har qulaylikning narxi bor:
- Kuzatish qiyin. "Bu son nega o'zgardi?" degan savolga javob izlaganda, to'g'ridan-to'g'ri chaqiruvni bir bosishda topasiz. Hodisada esa "kim tinglayapti?" deb butun loyihani qidirasiz.
- Unutilgan tinglovchi. Sahifadan olib tashlangan qism markazda tinglashda davom etsa, xotira bekorga band bo'ladi. Listener opsiyalari darsidagi
signalbilan tozalashni unutmang. - Ortiqcha murakkablik. Ikki funksiya bir-birini chaqirishi yetarli bo'lgan joyda event bus — keraksiz qavat.
Amaliy qoida: bitta joy bitta ishni chaqirsa — oddiy funksiya chaqiruvi. Bitta voqeaga ko'p va har xil qismlar javob bersa — hodisa.
Tekshirib ko'ring: Bron formasi yuborilganda faqat bitta ish bor: tasdiq matnini ko'rsatish. Aziz buning uchun
bron:yuborildihodisasi va event bus yasamoqchi. To'g'ri qarormi?
Javob
Yo'q. Bitta voqea — bitta javob: oddiy funksiya chaqiruvi (tasdiqniKorsat()) soddaroq, o'qish va xatoni topish oson. Hodisa uchinchi-to'rtinchi "tinglovchi" paydo bo'lganda yoki forma kodi tinglovchilar haqida bilmasligi kerak bo'lganda (masalan, alohida analitika skripti) foydali bo'ladi.
6.4 Bir martalik va to'xtatiladigan tinglovchilar
Listener opsiyalari darsidagi once va signal o'z hodisalaringizda ham xuddi shunday ishlaydi. «Bahor» aksiyasi: birinchi buyurtmaga ko'k choy bepul. Xabar faqat birinchi savat:qoshildi da chiqishi kerak:
<p id="aksiya" hidden>Birinchi buyurtma: ko'k choy bepul!</p>
<script>
const aksiya = document.getElementById("aksiya");
document.addEventListener("savat:qoshildi", (event) => {
aksiya.hidden = false;
console.log(`Aksiya: ${event.detail.nom} bilan choy bepul`);
}, { once: true });
document.addEventListener("savat:qoshildi", (event) => {
console.log(`Savatga: ${event.detail.nom}`);
});
for (const nom of ["Osh", "Manti"]) {
document.dispatchEvent(
new CustomEvent("savat:qoshildi", { detail: { nom } }),
);
}
</script>Konsolda:
Aksiya: Osh bilan choy bepul
Savatga: Osh
Savatga: MantiIkkinchi savat:qoshildi da aksiya tinglovchisi yo'q edi — once: true uni birinchi chaqiruvdan keyin o'zi olib tashladi. Oddiy tinglovchi esa ikkala hodisani ham eshitdi.
Event bus'da bu ayniqsa muhim. Markaz sahifa yopilguncha yashaydi. Unga tinglovchi qo'ygan qism (masalan, yopilgan oyna) o'chirilgan bo'lsa ham, tinglovchi qolib ketaveradi va har hodisada behuda ishlaydi. Qism yo'qolganda tinglovchilarini ham olib tashlang: removeEventListener yoki bitta AbortController ning abort() i bilan.
6.5 Klass bilan — oldindan qisqa ko'rinish
Real kodda EventTarget ko'pincha klass bilan kengaytiriladi: obyekt o'z ma'lumotini saqlaydi va o'zgarganda hodisa e'lon qiladi. Klasslarni 10-qismda o'rganamiz (class sintaksisi) — hozir bu kodni tushunish shart emas, faqat shaklini tanib qoling:
class Savat extends EventTarget {
taomlar = [];
qosh(taom) {
this.taomlar.push(taom);
this.dispatchEvent(
new CustomEvent("ozgardi", { detail: this.taomlar }),
);
}
}extends EventTarget — "Savat ham hodisa yubora oladigan obyekt bo'lsin". Bu naqsh keyinroq Web Components da va freymvorklarda tez-tez uchraydi.
7. DevTools'da o'z hodisalaringiz
O'z hodisangiz ishlamasa, ikkita yordamchi bor:
- Event Listeners yorlig'i (Elements paneli) brauzer hodisalari kabi o'zinikilarini ham ko'rsatadi:
savat:ochirishbandi va qaysi fayl, qaysi qatorda tinglanayotgani. - Console'da
getEventListeners(document)—documentdagi barcha tinglovchilar ro'yxati, hodisa nomlari bo'yicha guruhlangan. Nomdagi xatoni (qoshlidi) shunda darhol ko'rasiz.
getEventListeners(document);
// {click: Array(1), savat:ochirish: Array(1)}
monitorEvents(document, "savat:ochirish");
// har savat:ochirish hodisasi konsolga chiqadigetEventListeners va monitorEvents faqat DevTools konsolida ishlaydi (Klaviatura hodisalari darsida monitorEvents ni ko'rgansiz).
8. Ko'p uchraydigan xatolar
8.1 bubbles: true ni unutish
Hodisa elementda yuborildi, document da tinglanyapti — jimlik. Tuzatish: { bubbles: true } yoki hodisani tinglanayotgan joyning o'ziga yuborish.
8.2 Ma'lumot detail dan tashqarida
new CustomEvent("x", { nom: "Osh" }) → event.detail — null. Tuzatish: { detail: { nom: "Osh" } }.
8.3 Tinglovchi hodisadan keyin qo'yildi
dispatchEvent sinxron — kech qo'yilgan tinglovchi o'tib ketgan hodisani eshitmaydi. Tuzatish: tinglovchilarni avval qo'ying (odatda fayl boshida), keyin hodisa yuboring.
8.4 Nomdagi xato
"savat:qoshildi" va "savat:qoshlidi" — xato xabari yo'q, natija ham yo'q. Tuzatish: nomni bitta o'zgarmasda saqlang va getEventListeners bilan tekshiring.
8.5 Mantiqni soxta click ga bog'lash
Boshqa joydan "tugmani bosgandek" qilish uchun tugma.dispatchEvent(new MouseEvent("click")) yozish. Bu chalkash: tinglovchi haqiqiy bosish bilan kodni farqlay olmaydi, isTrusted esa false. Tuzatish: ishni funksiyaga chiqaring va ikkala joydan ham shu funksiyani chaqiring. Tugmani dasturdan bosish rostdan kerak bo'lsa — tugma.click() (u ham isTrusted: false, lekin brauzerning odatiy bosish xulqini to'liq bajaradi).
9. Mashqlar
1-mashq (oson): Yangi bron
bron:yangi nomli hodisa yarating va document ga yuboring. detail da mehmon ismi va odam soni bo'lsin: Ali, 4. Tinglovchi konsolga Yangi bron: Ali, 4 kishi chiqarsin.
<p>Bronlar paneli</p>
<script>
// shu yerga yozing
</script>Yechim
<p>Bronlar paneli</p>
<script>
document.addEventListener("bron:yangi", (event) => {
const { ism, odam } = event.detail;
console.log(`Yangi bron: ${ism}, ${odam} kishi`);
});
document.dispatchEvent(new CustomEvent("bron:yangi", {
detail: { ism: "Ali", odam: 4 },
}));
</script>Konsolda:
Yangi bron: Ali, 4 kishiTinglovchi yuborishdan oldin yozilgan — aks holda hodisa o'tib ketardi. bubbles kerak emas: hodisa to'g'ridan-to'g'ri document ga yuborildi.
2-mashq (o'rta): Savat hisoblagichi
Har taom kartasida "Savatga" tugmasi bor. Tugma bosilganda u savat:qoshildi hodisasini (detail — nom va narx) o'zidan yuborsin. Ikki alohida tinglovchi document da: biri savatdagi taomlar sonini, ikkinchisi jami summani yangilasin. Har qo'shilishda konsolga Qo'shildi: Osh chiqsin.
Ishora: taom ma'lumoti — kartaning data-nom va data-narx atributlarida (dataset), son uchun Number() (Matn ↔ son). Bosishni ham delegation bilan oling.
<p>Savatda: <output id="soni">0</output> ta,
jami <output id="jami">0</output> so'm</p>
<ul id="menyu">
<li data-nom="Osh" data-narx="35000">Osh
<button type="button">Savatga</button></li>
<li data-nom="Ko'k choy" data-narx="5000">Ko'k choy
<button type="button">Savatga</button></li>
</ul>
<script>
// shu yerga yozing
</script>Yechim
<p>Savatda: <output id="soni">0</output> ta,
jami <output id="jami">0</output> so'm</p>
<ul id="menyu">
<li data-nom="Osh" data-narx="35000">Osh
<button type="button">Savatga</button></li>
<li data-nom="Ko'k choy" data-narx="5000">Ko'k choy
<button type="button">Savatga</button></li>
</ul>
<script>
// 1. Tugmalar: faqat e'lon qiladi
const menyu = document.getElementById("menyu");
menyu.addEventListener("click", (event) => {
const tugma = event.target.closest("button");
if (tugma === null) {
return;
}
const { nom, narx } = tugma.closest("li").dataset;
tugma.dispatchEvent(new CustomEvent("savat:qoshildi", {
detail: { nom, narx: Number(narx) },
bubbles: true,
}));
});
// 2. Hisoblagich
let soni = 0;
document.addEventListener("savat:qoshildi", (event) => {
soni++;
document.getElementById("soni").textContent = soni;
console.log(`Qo'shildi: ${event.detail.nom}`);
});
// 3. Jami summa
let jami = 0;
document.addEventListener("savat:qoshildi", (event) => {
jami += event.detail.narx;
document.getElementById("jami").textContent = jami;
});
</script>Osh va ko'k choyni bir martadan bossangiz: "Savatda: 2 ta, jami 40000 so'm", konsolda Qo'shildi: Osh va Qo'shildi: Ko'k choy. Uchala qism bir-birini bilmaydi. Hisoblagichni o'chirsangiz, summa ishlashda davom etadi.
3-mashq (qiyin): Bronni bekor qilish — ruxsat bilan
Bronlar ro'yxatida har qatorda "Bekor qilish" tugmasi bor. Tugma bosilganda bron:bekor hodisasi cancelable bo'lib yuborilsin. Alohida "qoida" tinglovchisi data-tolangan="ha" li bronni bekor qilishga yo'l qo'ymasin va konsolga Oldindan to'langan: Malika — kassaga murojaat qiling yozsin. Ruxsat bo'lsa — qator o'chsin va Bekor qilindi: Ali chiqsin.
Yechim
<ul id="bronlar">
<li data-ism="Ali">Ali, 4 kishi, 19:00
<button type="button">Bekor qilish</button></li>
<li data-ism="Malika" data-tolangan="ha">Malika, 2 kishi, 20:00
<button type="button">Bekor qilish</button></li>
</ul>
<script>
const bronlar = document.getElementById("bronlar");
bronlar.addEventListener("click", (event) => {
const qator = event.target.closest("li");
if (!event.target.matches("button") || qator === null) {
return;
}
const ruxsat = qator.dispatchEvent(new CustomEvent("bron:bekor", {
detail: { ism: qator.dataset.ism },
bubbles: true,
cancelable: true,
}));
if (ruxsat) {
qator.remove();
console.log(`Bekor qilindi: ${qator.dataset.ism}`);
}
});
document.addEventListener("bron:bekor", (event) => {
if (event.target.dataset.tolangan === "ha") {
event.preventDefault();
const ism = event.detail.ism;
console.log(
`Oldindan to'langan: ${ism} — kassaga murojaat qiling`,
);
}
});
</script>Malika qatoridagi tugma: konsolda Oldindan to'langan: Malika — ..., qator joyida. Ali qatoridagi: Bekor qilindi: Ali, qator yo'qoladi. Ro'yxat kodi "to'lov" haqida hech narsa bilmaydi — qoida alohida tinglovchida. cancelable: true ni olib tashlab ko'ring: preventDefault ishlamay qoladi va Malikaning broni ham o'chadi.
4-mashq: Portfolio qadami — tema o'zgarganini e'lon qilish
addEventListener asoslari darsida mavzu tugmasi ikki ishni qiladi: temaniQoy ni chaqiradi va o'zining aria-pressed ini almashtiradi. Muammo: temaniQoy ni boshqa joydan chaqirsangiz, tugma buni bilmaydi. DevTools konsolida temaniQoy("qorongi") deb sinang — sayt qorong'i, tugma esa "bosilmagan" (aria-pressed="false") holida qoladi. Malika NVDA bilan noto'g'ri holatni eshitadi. Keyinroq tema boshqa joylardan ham o'zgaradi: tizim sozlamasidan (matchMedia) va saqlangan tanlovdan (11-qism).
Yechim — "kim o'zgartirgan bo'lsa ham, tema o'zgardi" degan hodisa:
temaniQoyoxiridadocumentgatema:ozgardihodisasini yuboring.detailda — haqiqiy tema (html.dataset.tema, bo'lmasa"tizim") va hisoblangancolor-scheme.documentda bitta tinglovchi: tugmaningaria-pressedinidetail.temaga qarab qo'ysin.- Tugmaning
clicktinglovchisidanaria-pressedni almashtiradigan qatorni olib tashlang — endi buni tinglovchi qiladi.
Yechim
asosiy.js dagi tema qismi:
function temaniQoy(tema) {
const html = document.documentElement;
if (tema === "yorug" || tema === "qorongi") {
html.dataset.tema = tema;
} else {
delete html.dataset.tema;
}
const sxema = getComputedStyle(html).colorScheme;
document.dispatchEvent(new CustomEvent("tema:ozgardi", {
detail: { tema: html.dataset.tema ?? "tizim", sxema },
}));
return sxema;
}
const temaTugma = document.getElementById("tema-tugma");
document.addEventListener("tema:ozgardi", (event) => {
const qorongi = event.detail.tema === "qorongi";
temaTugma?.setAttribute("aria-pressed", String(qorongi));
});
temaTugma?.addEventListener("click", () => {
const yoqilgan = temaTugma.getAttribute("aria-pressed") === "true";
temaniQoy(yoqilgan ? "yorug" : "qorongi");
});Kichik nusxada tekshiramiz: temaniQoy ni tugmasiz, to'g'ridan-to'g'ri chaqiramiz — xuddi DevTools konsolidan chaqirgandek:
<style>
:root { color-scheme: light dark; }
[data-tema="qorongi"] { color-scheme: dark; }
</style>
<button type="button" id="tema-tugma" aria-pressed="false">
Qorong'i rejim</button>
<script>
function temaniQoy(tema) {
const html = document.documentElement;
if (tema === "yorug" || tema === "qorongi") {
html.dataset.tema = tema;
} else {
delete html.dataset.tema;
}
const sxema = getComputedStyle(html).colorScheme;
document.dispatchEvent(new CustomEvent("tema:ozgardi", {
detail: { tema: html.dataset.tema ?? "tizim", sxema },
}));
return sxema;
}
const temaTugma = document.getElementById("tema-tugma");
document.addEventListener("tema:ozgardi", (event) => {
const qorongi = event.detail.tema === "qorongi";
temaTugma?.setAttribute("aria-pressed", String(qorongi));
console.log(`tema:ozgardi — ${event.detail.tema}`);
});
temaTugma?.addEventListener("click", () => {
const bosilgan = temaTugma.getAttribute("aria-pressed");
temaniQoy(bosilgan === "true" ? "yorug" : "qorongi");
});
console.log(temaTugma.getAttribute("aria-pressed"));
temaniQoy("qorongi");
console.log(temaTugma.getAttribute("aria-pressed"));
temaniQoy("kok");
console.log(temaTugma.getAttribute("aria-pressed"));
</script>Konsolda:
false
tema:ozgardi — qorongi
true
tema:ozgardi — tizim
falseTugma bosilmagan bo'lsa ham, temaniQoy("qorongi") dan keyin aria-pressed — "true". Noto'g'ri qiymat ("kok") tizim rejimiga qaytardi va hodisa haqiqiy holatni — tizim ni — e'lon qildi. Tugmani bosib ham ko'ring: har bosishda bitta tema:ozgardi qatori chiqadi.
Nega detail ga tema argumentini emas, html.dataset.tema ni yozdik? Hodisa "nima so'raldi" ni emas, "nima bo'ldi" ni aytishi kerak. Tinglovchilar haqiqiy holatga ishonsin.
Endi temaniQoy tugma haqida hech narsa bilmaydi, tugma esa kim temani o'zgartirganini bilmaydi. 11-qismda saqlangan temani tiklaganingizda tugma o'zi to'g'ri holatga keladi.
Commit: JS: tema o'zgarishini tema:ozgardi hodisasi bilan e'lon qil.
10. Real ishda
- Web Components — o'z HTML teglaringiz (
<bahor-savat>) tashqariga faqatCustomEventorqali "gapiradi". Bu kursning Web Components darsida. - Freymvorklar — Vue'dagi
emit, Svelte'dagidispatchva Angular'dagiEventEmitter— aynan shu g'oyaning o'z versiyalari. React'da esa ko'pincha funksiya (onChange) uzatiladi. Kursda alohida o'rganamiz. - Node.js — server tomonda
EventEmitterbor: oqimlar (stream), server ulanishlari hodisa bilan ishlaydi.EventTargetham Node'da bor — buni "Ishga tushir" bilan ko'rdingiz. - Analitika va kengaytmalar — sahifa kodi
savat:qoshildini e'lon qiladi, analitika skripti esa uni tinglaydi. Savat kodi analitika haqida hech narsa bilmaydi. - Intervyuda: "
CustomEventvaEventfarqi?", "dispatchEventsinxronmi?", "Event bus'ning kamchiliklari?" — tez-tez so'raladi.
Xulosa
new Event("nom")yokinew CustomEvent("nom", { detail })— o'z hodisangiz;dispatchEvent— yuborish,addEventListener— tinglash.dispatchEventsinxron: barcha tinglovchilar shu zahoti ishlaydi; kech qo'yilgan tinglovchi hodisani eshitmaydi.- Ma'lumot faqat
detailda;bubblesvacancelablestandart holatdafalse. cancelable+preventDefault→dispatchEventfalseqaytaradi — "ruxsat so'rash" naqshi.new EventTarget()— elementsiz hodisa markazi (event bus); Node'da ham bor. Ko'p qism bitta voqeaga javob bersa — hodisa, bittasi bo'lsa — oddiy chaqiruv.- Nom: kichik harf, prefiks bilan (
savat:qoshildi), brauzer nomlarisiz.
Keyingi dars: Sahifa hayot sikli va Page Visibility — sahifa yuklanishdan yopilishgacha qanday bosqichlardan o'tadi va "orqaga" tugmasi bosilganda nima bo'ladi.
Manbalar
- MDN: "Creating and dispatching events", "CustomEvent: CustomEvent() constructor" — developer.mozilla.org
- MDN: "EventTarget: dispatchEvent() method", "EventTarget: EventTarget() constructor" — developer.mozilla.org
- DOM Standard: "Dispatching events", "Interface CustomEvent" — dom.spec.whatwg.org
- Chrome DevTools: "Console Utilities API reference" (getEventListeners, monitorEvents) — developer.chrome.com
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!