Hodisalarga kirish

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:

  1. Elementga uning xatti-harakatini bildiruvchi maxsus atribut qo'shamiz (masalan, data-counter);
  2. 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>
Behavior patterni HTML'ni deklarativ qiladi: element o'zining xatti-harakatini atribut orqali "e'lon qiladi", JavaScript esa umumiy mantiqni bir joyda saqlaydi. Bu murakkab ilovalarni yig'ishda ajoyib ishlaydi โ€” komponentni sahifaga qo'yish uchun faqat kerakli atributlar bilan HTML yozasiz.

Delegatsiyaning afzalliklari

Delegatsiyaning kamchiliklari

Xulosa

Hodisa delegatsiyasi haqiqatan ham ajoyib naqsh. Uni qo'llash algoritmi:

  1. Konteynerga bitta ishlovchi qo'yamiz;
  2. Ishlovchi ichida event.target orqali hodisa qaysi elementda boshlanganini aniqlaymiz;
  3. Agar bizni qiziqtirgan element bo'lsa (masalan, mos tagName, class yoki data-* 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.