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:
- Blink hit testing orqali aniq targetni topadi (masalan, uchinchi
<li>) - Bubbling zanjiri boshlanadi:
li → ul → body → ... <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)- 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.