DOM — Event Bubbling, Capturing, Delegation

~20 daq · JavaScript · Brauzer API lari

Muammodan boshlaylik

Mahsulotlar ro'yxati bor — 100 ta element. Har biriga bosish hodisasi qo'shmoqchisiz:

const elementlar = document.querySelectorAll(".mahsulot");

elementlar.forEach(function(el) {
  el.addEventListener("click", function() {
    console.log(el.textContent);
  });
});

Bu ishlaydi. Lekin 100 ta element — 100 ta tinglash funksiyasi xotirada saqlanadi.

Yangi mahsulot dinamik qo'shilsa — unga ham alohida handler qo'shish kerak:

const yangi = document.createElement("li");
yangi.className = "mahsulot";
yangi.textContent = "Yangi mahsulot";
ro_yxat.appendChild(yangi);

yangi.addEventListener("click", ...); // ← unutsangiz — ishlamaydi

Bu yondashuv noqulay va xotira isrofi. Yechim — Event Delegation. Uni tushunish uchun avval Event Bubbling kerak.


Event Bubbling — hodisa yuqoriga ko'tariladi

HTML elementlar bir-birining ichida joylashadi — daraxt tuzilmasi. Hodisa yuz berganda, u faqat o'sha elementda qolmaydi — ota elementlarga ko'tarilib chiqadi.

Hayotiy o'xshatma

Hovuzga tosh tashlansangiz — to'lqin markazdan tashqariga tarqaladi. Eng kichik doiradan boshlab, tobora kattaroq doiralar hosil bo'ladi. Event Bubbling — xuddi shunday. Hodisa boshlangan elementdan boshlab, ota elementlarga, undan ham yuqoriga — document gacha ko'tariladi.

<div id="tashqi">
  <div id="ichki">
    <button id="tugma">Bos</button>
  </div>
</div>
document.getElementById("tugma").addEventListener("click", function() {
  console.log("tugma");
});
document.getElementById("ichki").addEventListener("click", function() {
  console.log("ichki div");
});
document.getElementById("tashqi").addEventListener("click", function() {
  console.log("tashqi div");
});

Tugma bosilganda — uch handler ham ishlaydi:

tugma       ← birinchi: hodisa yuz bergan joy
ichki div   ← ikkinchi: ota element
tashqi div  ← uchinchi: undan ham yuqori ota

Tugmani bosib, ota elementlardagi handlerlar ham ishlab ketdi. Bu — bubbling.


Event Delegation — bitta handler, hammasi uchun

Bubbling tufayli shunday qilish mumkin: ota elementga bitta handler qo'yiladi. Qaysi bola element bosilganini event.target orqali aniqlanadi.

Hayotiy o'xshatma

Katta mehmonxona kirishidagi bir qo'riqchi. Har bir xona eshigiga alohida qo'riqchi emas — biri kirishda turadi. Kim kirsa — tekshiradi: "Siz necha-nchi xona mehmonisiz?" Xona soni bo'yicha qaror qabul qiladi.

Event Delegation — o'sha bitta qo'riqchi. Ota element — kirish. event.target — kim kirdi.

const ro_yxat = document.querySelector("ul");

// 100 ta li uchun — bitta handler
ro_yxat.addEventListener("click", function(event) {
  const bosilgan = event.target.closest("li"); // ← qaysi li bosildi?

  if (!bosilgan) return; // ul ning o'zi bosilsa — e'tiborsiz

  console.log(bosilgan.textContent);
  bosilgan.classList.toggle("tanlangan");
});

Endi yangi element qo'shilsa — handler avtomatik ishlaydi. Yangi li ham ul ichida — bubbling tufayli ul ning handleri uni ham ko'radi.


event.target va event.currentTarget farqi

Bu ikki xususiyat bitta ko'rinadi — lekin har xil narsa ko'rsatadi.

event.target — hodisa haqiqatda yuz bergan element. Tugma bosilsa — tugma.

event.currentTarget — handler qo'shilgan element. Handler ul da bo'lsa — ul.

ro_yxat.addEventListener("click", function(event) {
  console.log(event.target);        // bosilgan li
  console.log(event.currentTarget); // har doim ul — handler shu yerda
});

Delegation da event.target ishlatiladi — haqiqatda kim bosilganini bilish uchun.


closest — to'g'ri elementni topish

event.target doim kutilgan element bo'lmasligi mumkin. <li> ichida <span> bo'lsa, <span> bosilganda event.target — span, li emas.

<li class="mahsulot">
  <span class="nomi">Noutbuk</span>
  <span class="narx">8 000 000</span>
</li>
ro_yxat.addEventListener("click", function(event) {
  // event.target = bosilgan span bo'lishi mumkin
  const mahsulot = event.target.closest(".mahsulot"); // ← eng yaqin .mahsulot ota

  if (!mahsulot) return;
  console.log(mahsulot.querySelector(".nomi").textContent);
});

closest(selektor) — element va uning ota elementlarini ko'rib, selectorga mos eng yaqinini qaytaradi. Topilmasa — null.


event.stopPropagation — bubbling ni to'xtatish

Ba'zan hodisani yuqoriga chiqarmaslik kerak. Masalan: modal oyna ichidagi tugma bosildi — modal yopilmasligi kerak. Lekin modal tashqi qatlamida click hodisasi modal yopadi.

const modal     = document.querySelector(".modal");
const modal_ichi = document.querySelector(".modal-ichi");

// Modal tashqariga bosilsa — yopilsin
modal.addEventListener("click", function() {
  modal.classList.remove("ochiq");
});

// Modal ichiga bosilsa — yopilmasin
modal_ichi.addEventListener("click", function(event) {
  event.stopPropagation(); // ← bubbling to'xtatildi, modal handler ishlamaydi
});

Nima uchun stopPropagation ehtiyotkorlik bilan ishlatiladi?

stopPropagation bubbling ni to'xtatadi — yuqoridagi handlerlar hech qachon ishlamaydi. Bu kutilmagan holatlarga olib kelishi mumkin: analytics, logging, boshqa komponentlar hodisani ko'ra olmaydi. Faqat zarur joyda ishlatiladi.


Capturing — pastga tushish fazasi

Hodisaning ikkita fazasi bor:

document
    ↓ capturing (pastga)
  tashqi div
    ↓
  ichki div
    ↓
  tugma  ← hodisa yuz berdi
    ↑ bubbling (yuqoriga)
  ichki div
    ↑
  tashqi div
    ↑
document

Odatda handlerlar bubbling fazasida ishlaydi. Capturing fazasida ishlatish uchun uchinchi argument true yoki { capture: true }:

el.addEventListener("click", handler, true); // capturing fazasida
el.addEventListener("click", handler, { capture: true }); // bir xil

Capturing — kamdan-kam kerak. Ko'pchilik holatlarda bubbling yetarli.


Tez-tez uchraydigan xatolar

Delegation da noto'g'ri element tekshirish:

// ❌ — faqat to'g'ridan li bosilsa ishlaydi, li ichidagi span bosilsa yo'q
ro_yxat.addEventListener("click", function(event) {
  if (event.target.tagName === "LI") {
    // span bosilsa — bu shart bajarilmaydi
  }
});

// ✅ — closest har holda to'g'ri li ni topadi
ro_yxat.addEventListener("click", function(event) {
  const li = event.target.closest("li");
  if (!li) return;
  // ...
});

stopPropagation ni haddan ko'p ishlatish:

// ❌ — har joyda stopPropagation — kutilmagan oqibatlar
el.addEventListener("click", function(event) {
  event.stopPropagation();
  // ...
});

// ✅ — faqat haqiqatan kerak bo'lgan joyda

Xulosa

Event Bubbling — hodisa yuz bergan elementdan boshlab, ota elementlarga ko'tarilib chiqadi.

Event Delegation — ota elementga bitta handler. event.target.closest() bilan qaysi bola bosilganini aniqlash. Ko'p element uchun ideal — xotira tejash, dinamik elementlar uchun ham ishlaydi.

event.target — hodisa yuz bergan element. event.currentTarget — handler qo'shilgan element.

event.stopPropagation() — bubbling ni to'xtatish. Ehtiyotkorlik bilan.


Keyingi dars

47-dars: DOM — forma bilan ishlash — input, submit, validation