Hodisalarga kirish

Ko'tarilish va tutish (bubbling va capturing)

Hodisalar tarqalishini (propagation) tushunmasdan haqiqiy hodisalar bilan ishlab bo'lmaydi. Bu darsda hodisaning uch fazasini, ayniqsa eng muhim bubbling (ko'tarilish) tamoyilini chuqur o'rganamiz.

Bubbling tamoyili

Bubbling tamoyili sodda: elementda hodisa sodir bo'lganda, avval o'sha elementning ishlovchilari, keyin uning ota-elementiniki, so'ng eng yuqorigacha barcha ota-elementlarniki ishga tushadi.

Quyidagicha uch ichma-ich element bor deylik: FORM > DIV > P. Har biriga ishlovchi biriktirilgan:

<style>
  body * { margin: 10px; border: 1px solid blue; }
</style>

<form onclick="alert('form')">FORM
  <div onclick="alert('div')">DIV
    <p onclick="alert('p')">P</p>
  </div>
</form>

Endi ichkaridagi <p> ustiga bossak, ketma-ket quyidagilar ishlaydi:

  1. avval <p>dagi onclick: alert('p');
  2. keyin tashqaridagi <div>dagi: alert('div');
  3. so'ng eng tashqaridagi <form>dagi: alert('form').

Hodisa ichki elementdan boshlanib, tepaga โ€” ota-elementlar bo'ylab "ko'tariladi" โ€” huddi suvdagi havo pufakchasi yuqoriga ko'tarilgandek. Shu sababdan bu jarayon bubbling (ko'tarilish, ba'zan "pufaklanish") deb ataladi.

Ko'p hodisalar ko'tariladi, lekin hammasi emas. Masalan, focus hodisasi ko'tarilmaydi. Bunday istisnolar kam va biz ularni maxsus holatlarda ko'ramiz. Umumiy qoida: deyarli barcha hodisalar ko'tariladi.

event.target โ€” asl manba

Ko'tarilishda muhim savol: hodisa aslida qaysi elementda boshlangan? Bunga event.target javob beradi โ€” bu hodisani boshlagan, eng ichkaridagi (chuqurdagi) element.

Buni this (yoki event.currentTarget) bilan solishtiring:

Masalan, faqat <form>ga bitta ishlovchi qo'ysak, formaning istalgan joyiga bosilganda hodisa unga ko'tarilib keladi:

form.onclick = function(event) {
  // this = event.currentTarget = form (ishlovchi shu yerda)
  // event.target = haqiqatan bosilgan ichki element
  event.target.style.backgroundColor = 'yellow';

  alert('target = ' + event.target.tagName + ', this = ' + this.tagName);
};

Agar <p>ga bossak: event.target โ€” P bo'ladi, lekin this (currentTarget) doim FORM bo'ladi. Agar to'g'ridan-to'g'ri formaning bo'sh joyiga bossak, u holda event.target === this (ikkovi ham FORM).

Bu ikki xossaning farqi hodisa delegatsiyasi (keyingi dars) uchun juda muhim: this orqali ishlovchi joyi, event.target orqali esa aynan qayerda voqea yuz berganini bilamiz.

Ko'tarilishni to'xtatish: stopPropagation

Ko'tarilish eng tepaga โ€” <html>, so'ng document obyektiga, ba'zan hatto windowgacha davom etadi: har bir ishlovchi navbat bilan ishga tushadi.

Ammo istalgan ishlovchi "hodisa to'liq qayta ishlandi" deb qaror qilib, ko'tarilishni to'xtata oladi. Buning uchun event.stopPropagation() metodi ishlatiladi:

<body onclick="alert('Bu ishlamaydi.')">
  <button onclick="event.stopPropagation()">Meni bosing</button>
</body>

Endi tugma bosilsa, <body>dagi onclick ishlamaydi, chunki ko'tarilish tugma darajasida to'xtatildi.

stopPropagation va stopImmediatePropagation farqi

event.stopPropagation() yuqoriga ko'tarilishni to'xtatadi, lekin joriy elementdagi boshqa ishlovchilarni to'xtatmaydi. Agar bir elementda bir nechta ishlovchi bo'lsa, ular baribir ishlaydi.

Joriy elementdagi qolgan barcha ishlovchilarni ham to'xtatish va ko'tarilishni ham bekor qilish uchun event.stopImmediatePropagation() ishlatiladi:

elem.addEventListener('click', (e) => {
  alert('Birinchi ishlovchi');
  e.stopImmediatePropagation();
});

elem.addEventListener('click', () => {
  // Bu ISHLAMAYDI, chunki stopImmediatePropagation chaqirilgan
  alert('Ikkinchi ishlovchi');
});
Ko'tarilishni asossiz to'xtatmang! stopPropagation "o'lik zonalar" (dead zone) yaratishi mumkin. Masalan, sahifadagi barcha kliklarni hisoblaydigan analitika tizimi hujjat darajasida clickni tinglaydi. Agar biror element ko'tarilishni to'xtatib qo'ysa, o'sha element ustidagi kliklar analitikaga tushmaydi โ€” natijada "o'lik" hisoblanmaydigan joylar paydo bo'ladi. Odatda ko'tarilishni to'xtatishga real ehtiyoj yo'q; agar ehtiyoj bo'lsa, muqobil yechim (masalan, maxsus hodisa yoki eventga qo'shimcha belgi qo'yish) yaxshiroq bo'lishi mumkin.

Capturing (tutish) fazasi

Bubbling โ€” hodisalar tarqalishining bir qismi. Aslida standart hodisa tarqalishining uch fazasi bor:

  1. Capturing (tutish) fazasi โ€” hodisa yuqoridan (windowdan) pastga, elementga qadar tushadi;
  2. Target (nishon) fazasi โ€” hodisa asl elementga (target) yetib keladi;
  3. Bubbling (ko'tarilish) fazasi โ€” hodisa elementdan yuqoriga qaytadi.

Ya'ni <td>ga bosilsa, hodisa avval yuqoridan (<html> > <body> > <table> > ...) elementgacha "tushadi" (capturing), so'ng elementga yetib, keyin yana yuqoriga "ko'tariladi" (bubbling), yo'lda ishlovchilarni ishga tushiradi.

Bugungacha biz faqat bubblingdan foydalandik, chunki capturing fazasi kamdan-kam kerak bo'ladi va odatda ko'rinmaydi. Odatiy usulda (onclick, oddiy addEventListener) qo'yilgan ishlovchilar capturing fazasini bilmaydi โ€” ular faqat target va bubbling fazalarida ishlaydi.

Capturing fazasida ishlovchi ushlash uchun addEventListenerning uchinchi argumenti captureni true qilish kerak:

// ikkala ko'rinish teng:
elem.addEventListener('click', handler, true);
elem.addEventListener('click', handler, { capture: true });

Agar capture: false (yoki umuman berilmasa โ€” bu standart), ishlovchi bubbling fazasida ishlaydi. Quyidagi misolda har bir elementga ikkita ishlovchi qo'yilgan โ€” biri capturingda, biri bubblingda:

for (let elem of document.querySelectorAll('*')) {
  // capturing fazasi
  elem.addEventListener('click', (e) => alert('Capturing: ' + elem.tagName), true);
  // bubbling fazasi
  elem.addEventListener('click', (e) => alert('Bubbling: ' + elem.tagName));
}

Eng ichki elementga bosilsa, avval barcha ota-elementlar tepadan pastga capturing ishlovchilarini ishga tushiradi, so'ng elementga yetib, keyin pastdan tepaga bubbling ishlovchilari ishlaydi.

event.eventPhase xossasi hodisa qaysi fazada ekanligini bildiradi: 1 โ€” capturing, 2 โ€” target, 3 โ€” bubbling. Amalda kamdan-kam ishlatiladi, chunki odatda fazani ishlovchini biriktirish paytida bilamiz.
Ishlovchini olib tashlashda (removeEventListener) capture qiymati ham mos kelishi kerak. Ya'ni addEventListener('click', h, true) bilan qo'yilgan ishlovchi removeEventListener('click', h, true) bilan olinadi โ€” trueni tushirib qoldirsangiz, boshqa ishlovchi olib tashlanadi (yoki hech narsa).

Xulosa