JavaScript’te Event Delegation: Daha Az Listener ile Daha Temiz ve Hızlı Arayüzler
Tek tek event listener bağlamak yerine tek noktadan yönet: dinamik listelerde sade, hızlı ve bakımı kolay çözüm.
Neden Event Delegation?
Tek tek butonlara/öğelere addEventListener eklemek kısa vadede kolay görünür; ama liste büyüdükçe ve DOM dinamikleştikçe iki sorun ortaya çıkar:
- Çok sayıda listener: Bellek ve yönetim maliyeti artar.
- Sonradan eklenen öğeler: Yeni gelen elemanlara listener bağlamayı unutmak sık olur.
Event delegation, olayların (event) DOM ağacında yukarı doğru “kabarcıklanması” (bubbling) özelliğinden yararlanır: Listener’ı üst kapsayıcıya koyar, tıklanan öğeyi içeride ayıklarsın.
Temel Örnek: Dinamik Todo Listesi
Aşağıdaki senaryoda listeye yeni li eklendikçe ayrı listener eklemiyoruz.
<ul id="todos">
<li data-id="1"><button data-action="toggle">Tamamla</button> <button data-action="delete">Sil</button></li>
</ul>
<form id="addForm">
<input id="todoText" placeholder="Yeni todo" />
<button>Ekle</button>
</form>
const todos = document.querySelector('#todos');
const addForm = document.querySelector('#addForm');
const todoText = document.querySelector('#todoText');
// Tek listener: tüm buton tıklamalarını yakalar
todos.addEventListener('click', (e) => {
const btn = e.target.closest('button');
if (!btn) return; // tıklanan yer button değilse
const li = btn.closest('li');
const action = btn.dataset.action;
if (action === 'toggle') {
li.classList.toggle('done');
}
if (action === 'delete') {
li.remove();
}
});
// Listeye eleman eklerken ekstra listener gerekmez
addForm.addEventListener('submit', (e) => {
e.preventDefault();
const text = todoText.value.trim();
if (!text) return;
const id = crypto.randomUUID();
const li = document.createElement('li');
li.dataset.id = id;
li.innerHTML = `
<button data-action="toggle">Tamamla</button>
<button data-action="delete">Sil</button>
<span>${text}</span>
`;
todos.appendChild(li);
todoText.value = '';
});
Buradaki küçük ama kritik detaylar
e.target.closest('button'): İkon/SVG gibi iç elemanlara tıklansa bile butonu bulur.data-action: “Hangi butona basıldı?” bilgisini okunur hale getirir.closest('li'): İşlem yapılacak satırı kolayca seçer.
Delegation Ne Zaman Uygun Değil?
Event delegation her yerde sihirli değildir:
- Bubbling olmayan eventler:
focus,blurgibi bazı eventler kabarcıklanmaz (çözüm:focusin/focusoutveya capture). - Çok özel etkileşimler: Her öğe için farklı state/closure gerekiyorsa tek listener karmaşıklaşabilir.
- Yüksek frekanslı eventler:
mousemovegibi eventlerde kapsayıcıya bağlamak gereksiz yoğunluk yaratabilir.
Capture ile İnce Ayar
Bazen event’i bubbling aşamasında değil, capture aşamasında yakalamak istersin:
document.addEventListener('click', (e) => {
// üstten aşağı yakalama
}, { capture: true });
Bu, özellikle “önce ben müdahale edeyim” dediğin global senaryolarda (ör. modal dışına tıklayınca kapat) iş görebilir.
Pratik İpucu: Tek Bir Router Gibi Düşün
Delegation’ı küçük bir “UI router” gibi kurgulayabilirsin:
todos.addEventListener('click', (e) => {
const actionEl = e.target.closest('[data-action]');
if (!actionEl) return;
const actions = {
toggle: () => actionEl.closest('li').classList.toggle('done'),
delete: () => actionEl.closest('li').remove(),
};
actions[actionEl.dataset.action]?.();
});
Bu yaklaşım büyüyen arayüzlerde if-else zincirlerini azaltır.
Sonuç
Event delegation, özellikle liste/tablolar, dinamik içerikler ve çok sayıda tıklanabilir öğe içeren UI’larda kodu sadeleştirir. Daha az listener ile daha az hata noktası, daha iyi bakım ve genellikle daha iyi performans elde edersin.