Hodisa delegatsiyasi
Bubbling va targeting bir kuchli hodisa qayta ishlash naqshiga โ hodisa delegatsiyasiga (event delegation) imkon beradi. Bu naqsh yordamida bir xil tarzda ishlov beriladigan ko'plab elementlarga har biriga alohida ishlovchi biriktirish o'rniga, bitta ishlovchi qo'yamiz.
Delegatsiya g'oyasi
Agar bizda umumiy usulda ishlov beriladigan ko'plab element bo'lsa, har biriga alohida ishlovchi biriktirish o'rniga, ularning umumiy ota-elementiga bitta ishlovchi qo'yamiz.
Bu ishlovchida event.target orqali aynan qaysi element bosilganini aniqlaymiz va shunga qarab ishlov beramiz. Bubbling tufayli ichki elementlardagi kliklar ota-elementga ko'tarilib keladi, shuning uchun bitta ishlovchi barchasini "tutadi".
G'oyaning oddiy misoli โ jadval (<table>). Uning ko'p kataklari (<td>) bor va biz bosilgan katakni belgilamoqchimiz. Har bir <td>ga ishlovchi qo'yish o'rniga, bitta ishlovchini <table>ga qo'yamiz:
let selectedTd;
table.onclick = function(event) {
let target = event.target; // qayerga bosildi?
if (target.tagName != 'TD') return; // TD emas? โ e'tibor bermaymiz
highlight(target); // katakni yoritamiz
};
function highlight(td) {
if (selectedTd) {
selectedTd.classList.remove('highlight'); // eski belgini olib tashlaymiz
}
selectedTd = td;
selectedTd.classList.add('highlight'); // yangi katakni belgilaymiz
}
Bu ishlovchi necha katak bo'lishidan qat'i nazar โ 10 ta ham, 10 000 ta ham โ ishlaydi. Bundan tashqari, kataklar dinamik qo'shilib/olib tashlansa ham (masalan, innerHTML orqali) qo'shimcha ishlovchi biriktirish shart emas.
event.target aniq bosilgan element bo'lgani uchun, u <td> ichidagi teg (masalan, <strong>) bo'lishi mumkin. Bunday holatlarda faqat target.tagName == 'TD' tekshiruvi yetmasligi mumkin โ kerak bo'lsa, target.closest('td') orqali eng yaqin <td>ni topish to'g'riroq bo'ladi.Amaliy misol: data-atributlar bilan
Delegatsiyaning kuchli tomoni โ data-* atributlaridan foydalanib, harakatlarni deklarativ qilish. Bir nechta tugma bo'lib, har biri boshqa amalni bajarsin. Har biriga alohida ishlovchi qo'yish o'rniga, ularning konteynerida bitta ishlovchi bilan hammasi boshqariladi:
<div id="menu">
<button data-action="save">Saqlash</button>
<button data-action="load">Yuklash</button>
<button data-action="search">Qidirish</button>
</div>
<script>
class Menu {
constructor(elem) {
this._elem = elem;
elem.onclick = this.onClick.bind(this); // (*) this bog'lanadi
}
save() { alert('saqlanmoqda'); }
load() { alert('yuklanmoqda'); }
search() { alert('qidirilmoqda'); }
onClick(event) {
let action = event.target.dataset.action;
if (action) {
this[action](); // masalan, this.save()
}
}
}
new Menu(menu);
</script>
Bu yerda data-action atributi tugma qaysi metodni chaqirishi kerakligini belgilaydi. Ishlovchida event.target.dataset.action orqali o'sha nomni olib, tegishli metodni chaqiramiz. Yangi tugma qo'shish uchun faqat HTML'ga data-action bilan bitta qator yozish yetarli โ JavaScriptga tegmaymiz.
(*) qatordagi bind(this) muhim: ishlovchi ichida this Menu nusxasini ko'rsatishi kerak (chunki this.save() chaqiramiz). Agar bind qilmasak, this bosilgan DOM element bo'lib qolardi va this.save topilmasdi."Behavior" (xatti-harakat) patterni
Delegatsiyani nafaqat "boshqarish" uchun, balki elementlarga deklarativ xatti-harakatlar (behaviors) qo'shish uchun ham ishlatish mumkin. Naqsh ikki qismdan iborat:
- Elementga uning xatti-harakatini bildiruvchi maxsus atribut qo'shamiz (masalan,
data-counter); - Butun hujjat (
document) darajasida bitta ishlovchi hodisalarni tinglaydi va agar hodisa shunday atributli elementda bo'lsa, kerakli amalni bajaradi.
Masalan, data-counter atributi bo'lgan har qanday tugma bosilganda o'z qiymatini oshirsin:
Hisoblagich: <input type="button" value="1" data-counter>
Boshqasi: <input type="button" value="2" data-counter>
<script>
document.addEventListener('click', function(event) {
if (event.target.dataset.counter != undefined) { // agar atribut bo'lsa
event.target.value++;
}
});
</script>
Endi data-counter atributli istalgan tugma avtomatik "hisoblagich" bo'ladi โ hatto sahifaga dinamik qo'shilgani ham. Bu naqshning kuchi: bir elementga bir nechta mustaqil xatti-harakat qo'yish mumkin, ular bir-biriga xalaqit bermaydi.
Yana bir misol โ data-toggle-id atributi bilan boshqa elementni ko'rsatish/yashirish:
<button data-toggle-id="subscribe-mail">Xat ro'yxatini ko'rsat</button>
<form id="subscribe-mail" hidden>
Elektron pochtangiz: <input type="email">
</form>
<script>
document.addEventListener('click', function(event) {
let id = event.target.dataset.toggleId;
if (!id) return;
let elem = document.getElementById(id);
elem.hidden = !elem.hidden;
});
</script>
Delegatsiyaning afzalliklari
- Xotira tejaladi: minglab elementlar o'rniga bitta ishlovchi โ DOM'da har biriga ishlovchi qo'shish shart emas;
- Kod qisqaradi: element qo'shish/o'chirishda ishlovchilarni qo'lda biriktirish/olib tashlash kerak emas;
- Dinamik elementlar: keyinroq qo'shilgan elementlar ham avtomatik ishlaydi (
innerHTMLva hokazo bilan); - Kamroq kod, kamroq xato:
innerHTMLyoki shunga o'xshash usullar bilan DOM'ni erkin o'zgartirish mumkin โ hodisalar buzilmaydi.
Delegatsiyaning kamchiliklari
- Hodisa ko'tarilishi (bubbling) shart. Ba'zi hodisalar ko'tarilmaydi. Shuningdek, past darajadagi ishlovchilar
event.stopPropagation()chaqirmasligi kerak, aks holda hodisa yuqoriga yetib bormaydi; - Konteyner darajasidagi ishlovchi barcha hodisalarga javob berishi kerak โ hatto bizni qiziqtirmaydiganlariga ham. Bu odatda arzon (yengil), lekin juda faol hodisalarda (masalan,
mousemove) hisobga olish kerak; - Ba'zan qo'shimcha tekshiruv kerak:
event.targetaniq kerakli element ekanini (yokiclosestorqali eng yaqin mos elementni) aniqlash lozim.
Xulosa
Hodisa delegatsiyasi haqiqatan ham ajoyib naqsh. Uni qo'llash algoritmi:
- Konteynerga bitta ishlovchi qo'yamiz;
- Ishlovchi ichida
event.targetorqali hodisa qaysi elementda boshlanganini aniqlaymiz; - Agar bizni qiziqtirgan element bo'lsa (masalan, mos
tagName,classyokidata-*atribut), hodisani qayta ishlaymiz.
Bu naqsh xotira va kod jihatidan tejamkor, dinamik elementlar bilan ajoyib ishlaydi va "behavior" naqshi orqali HTML'ni deklarativ qilishga imkon beradi.