14-bo‘lim
Hodisalar (events)
addEventListener, hodisa obyekti, ko'tarilish va tutish, delegatsiya, preventDefault va ishlovchini olib tashlash.
Ushbu bo‘lim mundarijasi
Foydalanuvchi tugmani bosdi, matn yozdi, sahifani aylantirdi - bularning har biri hodisa. Ilova shularga javob berib yashaydi.
document.body.innerHTML = `
<div id="ilova">
<ul id="royxat">
<li class="vazifa" data-id="1"><span>Non olish</span></li>
<li class="vazifa" data-id="2"><span>Kitob o'qish</span></li>
</ul>
<button id="qoshish">Qo'shish</button>
<a id="havola" href="https://example.com">Havola</a>
</div>`;
Birinchi ishlovchi #
const tugma = document.getElementById("qoshish");
let bosilgan = 0;
tugma.addEventListener("click", function () {
bosilgan++;
});
// Bosishni dastur orqali taqlid qilamiz
tugma.click();
tugma.click();
tugma.click();
console.log("Necha marta bosildi:", bosilgan);
Necha marta bosildi: 3
addEventListener - nima uchun onclick emasUchta usul bor, lekin faqat bittasi to'g'ri:
// 1. HTML da - kod va tuzilma aralashadi
<button onclick="qilish()">Bosing</button>
// 2. Xossa orqali - faqat BITTA ishlovchi
tugma.onclick = birinchi;
tugma.onclick = ikkinchi; // birinchisini o'chirdi!
// 3. addEventListener - bir nechta ishlovchi
tugma.addEventListener("click", birinchi);
tugma.addEventListener("click", ikkinchi); // ikkalasi ham ishlaydi
addEventListener afzalliklari:
- Bir nechta ishlovchi qo'shish mumkin;
- Sozlamalar bor (
once,capture,passive); removeEventListenerbilan olib tashlash mumkin;- HTML toza qoladi.
Hodisa obyekti #
const royxat = document.getElementById("royxat");
const natijalar = [];
royxat.addEventListener("click", function (hodisa) {
natijalar.push(`type: ${hodisa.type}`);
natijalar.push(`target: ${hodisa.target.tagName}`);
natijalar.push(`currentTarget: ${hodisa.currentTarget.tagName}`);
natijalar.push(`bubbles: ${hodisa.bubbles}`);
});
// Ichkaridagi span ni bosamiz
royxat.querySelector("span").click();
console.log(natijalar.join("\n"));
type: click
target: SPAN
currentTarget: UL
bubbles: true
target va currentTarget - eng ko'p chalkashtiriladigan juftlik| Xossa | Nima |
|---|---|
target | Hodisa boshlangan element (foydalanuvchi tekkani) |
currentTarget | Ishlovchi osilgan element |
Yuqoridagi misolda:
- Foydalanuvchi
<span>ni bosdi →target= SPAN; - Ishlovchi
<ul>da turibdi →currentTarget= UL.
Bu farq delegatsiyada hal qiluvchi ahamiyatga ega. Agar
target ni ishlatsangiz, foydalanuvchi qayerga aniq
tekkanini bilasiz - lekin bu ichkaridagi ixtiyoriy teg
bo'lishi mumkin.
Shuning uchun deyarli har doim closest bilan birga
ishlatiladi:
const vazifa = hodisa.target.closest(".vazifa");
Diqqat: arrow funksiyada this currentTarget emas.
Oddiy function da esa this === currentTarget.
Ko'tarilish (bubbling) #
const tartib = [];
document.getElementById("ilova").addEventListener("click", () => {
tartib.push("div (ilova)");
});
document.getElementById("royxat").addEventListener("click", () => {
tartib.push("ul (royxat)");
});
document.querySelector(".vazifa").addEventListener("click", () => {
tartib.push("li (vazifa)");
});
document.querySelector("span").addEventListener("click", () => {
tartib.push("span");
});
document.querySelector("span").click();
console.log("Bajarilish tartibi:");
console.log(tartib.join(" -> "));
Bajarilish tartibi:
span -> li (vazifa) -> ul (royxat) -> div (ilova)
Tutish bosqichi va to'xtatish #
const tartib = [];
document.getElementById("royxat").addEventListener("click", () => {
tartib.push("ul TUTISH");
}, { capture: true });
document.getElementById("royxat").addEventListener("click", () => {
tartib.push("ul ko'tarilish");
});
document.querySelector("span").addEventListener("click", () => {
tartib.push("span");
});
document.querySelector("span").click();
console.log(tartib.join(" -> "));
// stopPropagation - ko'tarilishni to'xtatadi
const tartib2 = [];
const yangiRoyxat = document.getElementById("royxat");
yangiRoyxat.addEventListener("click", () => tartib2.push("ul"));
const vazifa = document.querySelector(".vazifa");
vazifa.addEventListener("click", (h) => {
tartib2.push("li");
h.stopPropagation();
});
vazifa.click();
console.log("stopPropagation bilan:", tartib2.join(" -> "));
ul TUTISH -> span -> ul ko'tarilish
stopPropagation bilan: li
stopPropagation ni ehtiyotkorlik bilan ishlatingU hodisani to'xtatadi - yuqoridagi barcha ishlovchilar ishlamay qoladi.
Bu boshqa kodni sezdirmasdan buzadi:
// Modal oynani tashqariga bosilganda yopish
document.addEventListener("click", () => modalniYopish());
// Boshqa joyda kimdir yozgan:
tugma.addEventListener("click", (h) => h.stopPropagation());
// Endi modal yopilmaydi va sababi ko'rinmaydi
Ko'p hollarda stopPropagation o'rniga shartni tekshirish
kifoya:
document.addEventListener("click", (h) => {
if (h.target.closest(".modal")) {
return; // modal ichida - tegmaymiz
}
modalniYopish();
});
Yana bir metod bor - stopImmediatePropagation. U shu
elementdagi qolgan ishlovchilarni ham to'xtatadi. Undan
foydalanish uchun juda kuchli sabab kerak.
Hodisa delegatsiyasi #
const royxat = document.getElementById("royxat");
const bosilganlar = [];
// BITTA ishlovchi - barcha element uchun
royxat.addEventListener("click", (hodisa) => {
const vazifa = hodisa.target.closest(".vazifa");
if (!vazifa) {
return; // ro'yxatning bo'sh joyi bosildi
}
bosilganlar.push(vazifa.dataset.id);
});
royxat.querySelector("[data-id='1'] span").click();
royxat.querySelector("[data-id='2']").click();
// Yangi element qo'shamiz - ishlovchi kerak emas!
const yangi = document.createElement("li");
yangi.className = "vazifa";
yangi.dataset.id = "3";
yangi.textContent = "Yangi vazifa";
royxat.appendChild(yangi);
yangi.click();
console.log("Bosilgan id lar:", bosilganlar.join(", "));
console.log("Yangi element ham ishladimi?:", bosilganlar.includes("3"));
Bosilgan id lar: 1, 2, 3
Yangi element ham ishladimi?: true
Har bir elementga alohida ishlovchi osish o'rniga, bitta
ishlovchini otaga osasiz va closest bilan kerakligini
topasiz.
Afzalliklari:
| Muammo | Delegatsiya bilan |
|---|---|
| 1000 element = 1000 ishlovchi | Bitta ishlovchi |
| Yangi element ishlamaydi | Avtomatik ishlaydi |
| O'chirilgan element xotirada qoladi | Muammo yo'q |
Bu ayniqsa dinamik ro'yxatlarda muhim - siz element qo'shganda ishlovchi qo'shishni eslashingiz shart emas.
Naqshning to'liq shakli:
idish.addEventListener("click", (hodisa) => {
const tugma = hodisa.target.closest("[data-amal]");
if (!tugma || !idish.contains(tugma)) {
return;
}
switch (tugma.dataset.amal) {
case "ochirish": ochirish(tugma.dataset.id); break;
case "tahrirlash": tahrirlash(tugma.dataset.id); break;
}
});
Bitta ishlovchi butun ro'yxatni boshqaradi.
preventDefault #
const havola = document.getElementById("havola");
let ushlandi = false;
havola.addEventListener("click", (hodisa) => {
hodisa.preventDefault(); // sahifaga o'tishni bekor qiladi
ushlandi = true;
});
const hodisa = new MouseEvent("click", { bubbles: true, cancelable: true });
const natija = havola.dispatchEvent(hodisa);
console.log("Ishlovchi ishladimi? :", ushlandi);
console.log("defaultPrevented :", hodisa.defaultPrevented);
console.log("dispatchEvent natijasi:", natija);
console.log("Sahifa o'zgardimi? : yo'q");
Ishlovchi ishladimi? : true
defaultPrevented : true
dispatchEvent natijasi: false
Sahifa o'zgardimi? : yo'q
preventDefault va stopPropagation - butunlay boshqa narsa| Metod | Nimani to'xtatadi |
|---|---|
preventDefault | Brauzerning odatiy harakati |
stopPropagation | Hodisaning tarqalishi |
Odatiy harakatlar:
| Hodisa | Odatiy harakat |
|---|---|
click havolada | Yangi sahifaga o'tish |
submit formada | Sahifani qayta yuklash |
contextmenu | Kontekst menyu ochish |
keydown (Tab) | Keyingi maydonga o'tish |
Ikkalasi mustaqil: preventDefault chaqirsangiz ham hodisa
yuqoriga ko'tarilaveradi.
Eski kodda return false uchraydi - u ikkalasini ham bajaradi,
lekin faqat onclick xossasida. addEventListener da
ishlamaydi. Aniq yozing.
Ishlovchini olib tashlash #
const tugma = document.getElementById("qoshish");
let hisob = 0;
function ishlovchi() {
hisob++;
}
tugma.addEventListener("click", ishlovchi);
tugma.click();
tugma.click();
console.log("Olib tashlashdan oldin:", hisob);
tugma.removeEventListener("click", ishlovchi);
tugma.click();
console.log("Olib tashlagandan keyin:", hisob);
// once - bir marta ishlaydi va o'zi o'chadi
let birMarta = 0;
tugma.addEventListener("click", () => birMarta++, { once: true });
tugma.click();
tugma.click();
tugma.click();
console.log("once bilan:", birMarta);
Olib tashlashdan oldin: 2
Olib tashlagandan keyin: 2
once bilan: 1
tugma.addEventListener("click", () => console.log("salom"));
tugma.removeEventListener("click", () => console.log("salom")); // ISHLAMAYDI
Ikki arrow funksiya bir xil ko'rinsa ham, boshqa obyekt.
removeEventListener aynan o'sha havolani talab qiladi.
Uch yechim:
// 1. Nomlangan funksiya
function ishlovchi() { }
tugma.addEventListener("click", ishlovchi);
tugma.removeEventListener("click", ishlovchi);
// 2. once - o'zi o'chadi
tugma.addEventListener("click", () => {}, { once: true });
// 3. AbortController - bir nechtasini birdan
const boshqaruv = new AbortController();
tugma.addEventListener("click", () => {}, { signal: boshqaruv.signal });
oyna.addEventListener("resize", () => {}, { signal: boshqaruv.signal });
boshqaruv.abort(); // ikkalasi ham olib tashlanadi
Uchinchi usul komponent "o'lganda" hamma ishlovchini bir yo'la tozalash uchun ideal.
AbortController bilan tozalash #
const tugma = document.getElementById("qoshish");
const boshqaruv = new AbortController();
let birinchi = 0;
let ikkinchi = 0;
tugma.addEventListener("click", () => birinchi++, { signal: boshqaruv.signal });
tugma.addEventListener("click", () => ikkinchi++, { signal: boshqaruv.signal });
tugma.click();
console.log("abort dan oldin:", birinchi, ikkinchi);
boshqaruv.abort();
tugma.click();
tugma.click();
console.log("abort dan keyin:", birinchi, ikkinchi);
console.log("Ikkalasi ham to'xtadimi?:", birinchi === 1 && ikkinchi === 1);
abort dan oldin: 1 1
abort dan keyin: 1 1
Ikkalasi ham to'xtadimi?: true
Klaviatura hodisalari #
const maydon = document.createElement("input");
document.body.appendChild(maydon);
const bosilganlar = [];
maydon.addEventListener("keydown", (hodisa) => {
bosilganlar.push(`${hodisa.key} (ctrl: ${hodisa.ctrlKey})`);
if (hodisa.key === "Enter") {
bosilganlar.push(" -> Enter bosildi");
}
});
maydon.dispatchEvent(new KeyboardEvent("keydown", { key: "a" }));
maydon.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" }));
maydon.dispatchEvent(new KeyboardEvent("keydown", { key: "s", ctrlKey: true }));
console.log(bosilganlar.join("\n"));
a (ctrl: false)
Enter (ctrl: false)
-> Enter bosildi
s (ctrl: true)
key ishlating, keyCode emasEski kodda hodisa.keyCode === 13 uchraydi. keyCode
standartdan chiqarilgan va turli klaviaturada farq qiladi.
key o'qiladigan nom beradi:
| Tugma | key qiymati |
|---|---|
| Enter | "Enter" |
| Escape | "Escape" |
| Bo'shliq | " " |
| O'q tugmalar | "ArrowUp", "ArrowDown" |
| Harf | "a", "A" |
Modifikatorlar alohida xossalarda: ctrlKey, shiftKey,
altKey, metaKey (Mac da Cmd).
Klaviatura qisqartmasi:
if (hodisa.ctrlKey && hodisa.key === "s") {
hodisa.preventDefault(); // brauzerning "saqlash" oynasini bekor qiladi
saqlash();
}
Mac da Cmd+S uchun metaKey ni ham tekshiring:
const boshqaruvTugmasi = hodisa.ctrlKey || hodisa.metaKey;
Maxsus hodisalar #
const idish = document.getElementById("ilova");
let qabulQilingan = null;
idish.addEventListener("vazifaQoshildi", (hodisa) => {
qabulQilingan = hodisa.detail;
});
// O'z hodisamizni yaratamiz va yuboramiz
const hodisa = new CustomEvent("vazifaQoshildi", {
detail: { id: 42, matn: "Yangi vazifa" },
bubbles: true
});
document.querySelector(".vazifa").dispatchEvent(hodisa);
console.log("Qabul qilindi:", qabulQilingan !== null);
console.log("id :", qabulQilingan.id);
console.log("matn :", qabulQilingan.matn);
Qabul qilindi: true
id : 42
matn : Yangi vazifa
addEventListenerbilan tugmaga ishlovchi osing.onclickvaaddEventListenerfarqini tushuntiring.targetvacurrentTargetfarqini misolda ko'rsating.- To'rt darajali ko'tarilish tartibini kuzating.
capture: truebilan tutish bosqichini sinang.stopPropagationning zararli tomonini tushuntiring.- Delegatsiya bilan bitta ishlovchida butun ro'yxatni boshqaring.
- Yangi qo'shilgan element ham ishlashini isbotlang.
preventDefaultbilan havolaga o'tishni bekor qiling.AbortControllerbilan ikki ishlovchini birdan olib tashlang.
Xulosa #
addEventListenerishlating - bir nechta ishlovchi va sozlamalar beradi.target- tegilgan element,currentTarget- ishlovchi osilgan element.- Hodisa uch bosqichdan o'tadi: tutish → nishon → ko'tarilish.
stopPropagationboshqa kodni sezdirmasdan buzishi mumkin.- Delegatsiya - bitta ishlovchi butun ro'yxat uchun.
- Delegatsiyada yangi element avtomatik ishlaydi.
preventDefaultodatiy harakatni,stopPropagationtarqalishni to'xtatadi.- Anonim funksiyani
removeEventListenerbilan olib bo'lmaydi. once: trueishlovchini bir martadan keyin o'chiradi.AbortControllerbir nechta ishlovchini birdan tozalaydi.- Klaviaturada
keyishlating,keyCodeemas. CustomEventbilan o'z hodisalaringizni yarating.
Keyingi bo'limda formalar va validatsiyani ko'rib chiqamiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.