Series: JavaScript Basics Lesson 58

Event delegation

Event delegation texnikasi: bitta ota-elementga listener qo'yib bubbling orqali barcha bola elementlarni boshqarish, nega bu xotira/performance va dinamik elementlar uchun foydali, closest() bilan to'g'ri nishonni topish, va bubbling qilmaydigan hodisalardagi cheklov.

58-dars

Event delegation

Event delegation — har bir bola elementga alohida-alohida addEventListener qo'yish o'rniga, bitta listener'ni ota-elementga qo'yib, Event bubbling mexanizmidan foydalanib, ichidagi istalgan bola elementdagi hodisani ushlab olish texnikasi.

<ul id="list">
  <li>Olma</li>
  <li>Nok</li>
  <li>Uzum</li>
</ul>

Yomon usul — har bir <li>ga alohida listener:

document.querySelectorAll('li').forEach(li => {
  li.addEventListener('click', () => console.log(li.textContent));
});

Delegation usuli — faqat <ul>ga bitta listener:

document.getElementById('list').addEventListener('click', (event) => {
  if (event.target.tagName === 'LI') {
    console.log(event.target.textContent);
  }
});

Ikkovi ham bir xil natija beradi — istalgan <li> bosilganda, uning matni chiqadi. Lekin ikkinchisi — bitta listener orqali ishlaydi, chunki <li> bosilganda hodisa bubbling qilib <ul>gacha ko'tariladi (oldingi mavzu), va aynan shu yerda event.target orqali qaysi <li> bosilgani aniqlanadi.


1. Nega bu kerak — nima uchun har birga alohida listener yomon?

Uch sabab bor:

1. Xotira va performance

Har bir addEventListener chaqiruvi — brauzer xotirasida bitta yozuv (listener obyekti + closure orqali ushlab turilgan callback funksiya). Agar sahifada 1000 ta <li> bo'lsa — 1000 ta alohida listener, 1000 ta xotira yozuvi. Delegation bilan — bitta listener, xotira sarfi deyarli sezilmaydi.

2. Dinamik elementlar muammosi

Bu — eng muhim sabab. Tasavvur qiling:

document.querySelectorAll('li').forEach(li => {
  li.addEventListener('click', () => console.log(li.textContent));
});

// Keyinroq, yangi element qo'shildi:
const newLi = document.createElement('li');
newLi.textContent = 'Shaftoli';
document.getElementById('list').appendChild(newLi);

Yangi qo'shilgan newLini bossangiz — hech narsa chiqmaydi! Chunki forEach orqali listener qo'yish — o'sha paytda DOM'da mavjud bo'lgan elementlarga bir martalik ishlov berish edi. Yangi element — bu jarayondan keyin qo'shilgan, unga hech kim listener qo'ymagan.

Delegation'da bu muammo umuman yo'q:

document.getElementById('list').addEventListener('click', (event) => {
  if (event.target.tagName === 'LI') {
    console.log(event.target.textContent);
  }
});
// Yangi <li> qo'shilsa ham — ishlayveradi, chunki listener <ul>da,
// va yangi <li>dan chiqqan click ham xuddi shu <ul>ga bubbling qiladi

Bu — SPA (Single Page Application), dinamik ro'yxatlar, chat ilovalari kabi elementlar doimiy qo'shilib-o'chirilib turadigan interfeyslarda hal qiluvchi ahamiyatga ega.

3. Kodni soddalashtirish

Bitta joyda, bitta mantiq — har bir element uchun alohida funksiya yozish, ularni sinxronlashtirib turish shart emas.


2. Brauzer/V8 darajasida — nima farq qiladi

Alohida listener'lar holatida, Blink engine har bir <li> node uchun o'zining EventListenerMapida alohida yozuv saqlaydi (bu — avval ko'rgan Hash Map'ga o'xshash tuzilma, node → listener ro'yxati). 1000 ta <li> — 1000 ta alohida map yozuvi, va har biri o'zining closure orqali ushlab turilgan funksiya obyektiga ega (bu — avvalda ko'rgan closure mexanizmi, heap'da saqlanadi).

Delegation holatida — faqat bitta node (<ul>)ning EventListenerMap'ida bitta yozuv bor. <li> bosilganda:

  1. Blink hit testing orqali aniq targetni topadi (masalan, uchinchi <li>)
  2. Bubbling zanjiri boshlanadi: li → ul → body → ...
  3. <ul>ga yetganda, u yerdagi yagona listener chaqiriladi, unga event obyekti uzatiladi, event.target — hali ham aynan bosilgan <li>ni ko'rsatib turadi (Event bubbling mavzusida ko'rganimizdek, target bubbling davomida o'zgarmaydi)
  4. Sizning kodingiz ichida if (event.target.tagName === 'LI') — bu shunchaki oddiy JS tekshiruv, hech qanday maxsus brauzer mexanizmi emas, siz o'zingiz "qaysi element bosilganini" aniqlaysiz

Ya'ni: delegation — brauzerga yangi imkoniyat qo'shmaydi. U faqat sizga allaqachon mavjud bo'lgan bubbling mexanizmidan aqlli foydalanish usulini beradi — "qayerda tinglashni" tashqariga, ota elementga ko'chirish orqali.


3. To'g'ri nishonni aniqlash — closest() muhimligi

Amaliyotda muammo tug'iladigan joy — agar <li> ichida yana ichki element bo'lsa:

<li><span>Olma</span> <b>(yangi)</b></li>
document.getElementById('list').addEventListener('click', (event) => {
  console.log(event.target.tagName); // agar <b>ni bossa — "B", <li> emas!
});

Foydalanuvchi <b>ni bossa, event.target — <b> bo'ladi, <li> emas. event.target.tagName === 'LI' tekshiruvi ishlamay qoladi. To'g'ri yechim — closest() metodi, u eng yaqin ota-elementni, berilgan selektor bo'yicha, yuqoriga qarab qidiradi:

document.getElementById('list').addEventListener('click', (event) => {
  const li = event.target.closest('li');
  if (li) {
    console.log(li.textContent); // istalgan ichki elementni bossa ham ishlaydi
  }
});

closest() — DOM daraxtida event.targetdan boshlab, parentElement zanjiri bo'ylab yuqoriga yurib, birinchi mos keluvchi elementni topadi (bu — Node prototipida ta'minlangan, oldingi mavzuda ko'rgan prototype chain orqali). Bu — amaliyotda eng ko'p ishlatiladigan, to'g'ri delegation pattern.


4. Cheklov: bubbling qilmaydigan hodisalarda delegation ishlamaydi

Event bubbling mavzusida aytilganidek, focus, blur kabi hodisalar bubbling qilmaydi. Shuning uchun ularga oddiy delegation ishlamaydi:

// ISHLAMAYDI — focus bubbling qilmaydi
document.getElementById('form').addEventListener('focus', (e) => {...});

// TO'G'RI — focusin ishlatiladi, u focus'ning bubbling versiyasi
document.getElementById('form').addEventListener('focusin', (e) => {...});

Xulosa jadvali

Alohida listener Event delegation
Listener soni Har element uchun bittadan Bitta, ota-elementda
Xotira sarfi Katta (ko'p element bo'lsa) Minimal
Dinamik elementlar Ishlamaydi (qayta listener kerak) Avtomatik ishlaydi
Ichki elementlar bilan To'g'ridan-to'g'ri ishlaydi closest() kerak bo'lishi mumkin
Bubbling qilmaydigan hodisalar Muammo yo'q Ishlamaydi (focusin kabi variant kerak)

Event delegation — Event bubbling mexanizmidan foydalanib, bitta ota-elementga bitta listener qo'yish orqali, ichidagi cheksiz sonli (hozirgi va kelajakdagi) bola elementlarning hodisalarini boshqarish texnikasi, event.target (yoki closest()) orqali aniq qaysi element bosilganini aniqlash bilan birga ishlaydi.