Kısa cevap: Bir olay DOM ağacında üç evreden geçer. Önce Window düzeyinden hedefin ebeveynine kadar aşağı iner (yakalama, capturing), sonra hedefin kendisine ulaşır (hedef evresi), ardından olay kabarcıklanıyorsa aynı yolu tersine izleyerek yukarı çıkar (kabarcıklanma, bubbling). addEventListener() varsayılan olarak kabarcıklanma evresini dinler, capture: true ile yakalama evresine geçersiniz. stopPropagation() yayılmayı keser, preventDefault() ise yayılmaya dokunmadan tarayıcının varsayılan eylemini iptal eder. Olay delegasyonu da tam bu kabarcıklanmaya dayanır: Çok sayıda alt öğe yerine ortak ebeveyne tek bir dinleyici koyar, tıklananı event.target ile bulursunuz.
Bu yazıda evrelerin sırasını eventPhase değerleriyle, üç durdurma ve iptal yöntemi arasındaki farkı, target ile currentTarget ayrımını, closest() ile sağlam bir delegasyon desenini ve shadow DOM sınırında composedPath() davranışını MDN belgelerine dayanarak anlatıyoruz. Dinleyici ekleme seçenekleri için addEventListener yazısına, serinin bütün konuları için Web API öğrenme rehberi sayfasına bakabilirsiniz.
Olay akışının üç evresi

MDN Event.eventPhase sayfasında evreleri dört sabitle tanımlar. NONE (0) olayın şu anda işlenmediğini gösterir. CAPTURING_PHASE (1) sırasında olay hedefin atalarından geçer: Window, ardından Document, ardından html öğesi ve sırayla hedefin ebeveynine kadar iner; yalnızca yakalama moduyla kaydedilmiş dinleyiciler burada çalışır. AT_TARGET (2) olayın hedefe ulaştığı andır; olay kabarcıklanmıyorsa işlem bu evreyle biter. BUBBLING_PHASE (3) sırasında olay ataları ters sırayla, ebeveynden başlayıp Window nesnesine kadar dolaşır.
| Sabit | Değer | Hangi dinleyiciler çalışır |
|---|---|---|
| Event.NONE | 0 | Hiçbiri, olay işlenmiyor |
| Event.CAPTURING_PHASE | 1 | Atalardaki capture: true dinleyicileri, yukarıdan aşağıya |
| Event.AT_TARGET | 2 | Hedefin kendisine eklenmiş dinleyiciler |
| Event.BUBBLING_PHASE | 3 | Atalardaki varsayılan dinleyiciler, aşağıdan yukarıya, yalnızca bubbles true ise |
Bir evre içindeki sıra da tanımlıdır: MDN'nin DOM olayları sayfasına göre aynı evrede dinleyiciler kaydedildikleri sırayla çağrılır. Hedefe doğrudan eklenmiş bir dinleyici için olay yakalama ya da kabarcıklanma evresinde değil, hedef evresindedir. Aşağıdaki küçük deneme sabitlerin değerlerini ve hedefe eklenmiş bir dinleyicinin gördüğü evreyi gösterir; standart Event ve EventTarget kullandığı için Node.js 24.4.0 ile çalıştırıp doğruladık.
const hedef = new EventTarget();
const e = new Event("deneme");
console.log(e.bubbles, e.cancelable, e.composed); // false false false
console.log(e.eventPhase); // 0
hedef.addEventListener("deneme", (ev) => console.log(ev.eventPhase)); // 2
hedef.dispatchEvent(e);
console.log(Event.CAPTURING_PHASE, Event.AT_TARGET, Event.BUBBLING_PHASE); // 1 2 3Kendi oluşturduğunuz olaylarda bubbles varsayılan olarak false olduğuna dikkat edin. Bir alt öğeden gönderdiğiniz özel olayın ebeveynde yakalanmasını istiyorsanız olayı bubbles: true ile oluşturmanız gerekir; bu konu CustomEvent yazısında örnekleniyor.
Yakalama ve kabarcıklanmayı sayfada görmek
Aşağıdaki HTML iç içe üç kutu içerir. Her kutuya hem yakalama hem kabarcıklanma dinleyicisi eklenir ve dinleyiciler currentTarget kimliğini ve evre adını bir listeye yazar. MDN'nin eventPhase örneği aynı fikri dört katmanlı bir yapıyla gösterir; biz onu sadeleştirdik. Bu örnek tarayıcıya özgüdür ve çalıştırılmadı, beklenen sıra MDN'nin evre tanımından çıkarılmıştır.
<div id="dis">
dış
<div id="orta">
orta
<button type="button" id="ic">iç düğme</button>
</div>
</div>
<ol id="kayit"></ol>const kayit = document.getElementById("kayit");
const evreler = ["none", "capturing", "target", "bubbling"];
function yaz(event) {
const li = document.createElement("li");
li.textContent = `${event.currentTarget.id}: ${evreler[event.eventPhase]}`;
kayit.append(li);
}
for (const id of ["dis", "orta", "ic"]) {
const el = document.getElementById(id);
el.addEventListener("click", yaz, { capture: true });
el.addEventListener("click", yaz);
}Düğmeye tıklandığında önce dis ve orta üzerindeki yakalama dinleyicileri capturing olarak, sonra düğmenin iki dinleyicisi target olarak, en son orta ve dis üzerindeki varsayılan dinleyiciler bubbling olarak listeye düşer. Düğmede iki dinleyici de hedef evresinde çalıştığı için aralarındaki sıra kayıt sırasıdır.
stopPropagation, stopImmediatePropagation ve preventDefault

Bu üç yöntem sık karıştırılır, çünkü üçü de olayı bir şekilde "durdurur". Aralarındaki fark neyin durduğundadır. stopPropagation() olayın yakalama ve kabarcıklanma yolunda sonraki öğelere ulaşmasını engeller; ancak MDN'nin açıkça belirttiği gibi aynı öğedeki diğer dinleyicileri durdurmaz ve bağlantı tıklaması gibi varsayılan davranışları da engellemez. stopImmediatePropagation() bir adım ileri gider: Aynı öğede henüz çalışmamış dinleyiciler de dahil olmak üzere kalan hiçbir dinleyici çağrılmaz. preventDefault() ise yayılmayla ilgilenmez; olay yoluna devam eder ama tarayıcının varsayılan eylemi, örneğin sayfa kaydırma, bağlantıya gitme ya da form gönderme gerçekleşmez.
| Yöntem | Diğer öğelere yayılım | Aynı öğedeki sonraki dinleyiciler | Varsayılan eylem |
|---|---|---|---|
| stopPropagation() | Durur | Çalışır | Gerçekleşir |
| stopImmediatePropagation() | Durur | Çalışmaz | Gerçekleşir |
| preventDefault() | Devam eder | Çalışır | İptal edilir (olay iptal edilebilirse) |
Aynı hedefteki dinleyiciler arasındaki farkı tarayıcı olmadan da görebilirsiniz. Aşağıdaki örnek iki ayrı hedefte üçer ve ikişer dinleyici kaydeder. İlkinde ikinci dinleyici stopImmediatePropagation() çağırdığı için üçüncü çalışmaz; ikincisinde ise stopPropagation() aynı hedefteki ikinci dinleyiciyi engellemez. Örnek Node.js 24.4.0 ile çalıştırıldı.
const hedef = new EventTarget();
const sira = [];
hedef.addEventListener("tikla", () => sira.push("birinci"));
hedef.addEventListener("tikla", (e) => {
sira.push("ikinci");
e.stopImmediatePropagation();
});
hedef.addEventListener("tikla", () => sira.push("ucuncu"));
hedef.dispatchEvent(new Event("tikla"));
console.log(sira.join(", ")); // birinci, ikinci
const hedef2 = new EventTarget();
const sira2 = [];
hedef2.addEventListener("tikla", (e) => {
sira2.push("birinci");
e.stopPropagation();
});
hedef2.addEventListener("tikla", () => sira2.push("ikinci"));
hedef2.dispatchEvent(new Event("tikla"));
console.log(sira2.join(", ")); // birinci, ikincipreventDefault() yalnızca iptal edilebilir olaylarda işe yarar. Tarayıcının kullanıcı etkileşiminden ürettiği click, wheel ve beforeunload gibi olaylar iptal edilebilir; betikle oluşturulan olaylar ise oluşturulurken cancelable: true verilmediyse iptal edilemez ve preventDefault() çağrısı etkisiz kalır. dispatchEvent() dönüş değeri de bunu yansıtır: Olay iptal edilebilir ve bir dinleyici preventDefault() çağırdıysa false, aksi halde true döner.
const hedef = new EventTarget();
hedef.addEventListener("kaydet", (e) => e.preventDefault());
const iptalEdilemez = new Event("kaydet");
console.log(hedef.dispatchEvent(iptalEdilemez)); // true
console.log(iptalEdilemez.defaultPrevented); // false
const iptalEdilebilir = new Event("kaydet", { cancelable: true });
console.log(hedef.dispatchEvent(iptalEdilebilir)); // false
console.log(iptalEdilebilir.defaultPrevented); // trueMDN birden çok olay türünü işleyen dinleyicilerin preventDefault() çağırmadan önce event.cancelable değerine bakmasını önerir. Pasif dinleyicilerde de preventDefault() etkisizdir; bu ayrıntı addEventListener yazısındaki passive bölümünde anlatılıyor. Eski kodlarda görebileceğiniz event.cancelBubble = true ataması stopPropagation() için tarihsel bir takma addır ve MDN onu kullanımdan kaldırılmış olarak işaretler; event.returnValue = false da benzer şekilde yerini preventDefault() ve defaultPrevented özelliğine bırakmış tarihsel bir özelliktir.
target ile currentTarget arasındaki fark
Delegasyonu anlamak için bu iki özelliğin farkı şarttır. event.target olayın ilk gönderildiği nesnedir; kullanıcı bir listedeki li içindeki span öğesine tıkladıysa target o span olur. event.currentTarget ise şu anda çalışan dinleyicinin bağlı olduğu nesnedir; dinleyiciyi ul üzerine koyduysanız currentTarget her zaman ul olur. Olay kabarcıklanırken target değişmez, currentTarget her öğede değişir. MDN ayrıca currentTarget değerinin yalnızca dinleyici çalışırken dolu olduğunu, dışarıda null olduğunu hatırlatır.
Olay delegasyonu nasıl kurulur?
Delegasyonda dinleyiciyi tek tek her alt öğeye eklemek yerine onları kapsayan ortak bir ebeveyne bir kez eklersiniz. Alt öğedeki tıklama kabarcıklanarak ebeveyne ulaşır, dinleyici de event.target üzerinden hangi öğenin tıklandığını bulur. Bu yaklaşımın iki pratik kazancı vardır: Yüzlerce satırlık bir liste için yüzlerce fonksiyon kaydı yerine tek kayıt yapılır ve listeye sonradan eklenen öğeler, ayrıca dinleyici eklemeye gerek kalmadan otomatik olarak kapsanır. MDN'nin Event.target sayfasındaki örnek tam olarak budur: Dinleyici ul üzerinde durur, tıklanan li gizlenir.
Gerçek uygulamada tıklanan öğe çoğu zaman aradığınız öğenin içindeki bir simge ya da metin parçasıdır. Bu yüzden doğrudan event.target ile karşılaştırmak yerine Element.closest() kullanmak daha sağlamdır. closest() öğenin kendisinden başlayarak belge köküne doğru ilerler ve verilen CSS seçicisine uyan ilk öğeyi döndürür; bulamazsa null verir, geçersiz bir seçici verilirse SyntaxError fırlatır. Bulunan öğenin gerçekten dinleyicinin bağlı olduğu kapsayıcının içinde olduğunu da contains() ile doğrulamak, iç içe aynı sınıfı kullanan yapılarda sürprizleri önler.
<ul id="gorevler">
<li data-id="1"><span class="ad">Faturayı öde</span> <button class="sil" type="button">Sil</button></li>
<li data-id="2"><span class="ad">Yedek al</span> <button class="sil" type="button">Sil</button></li>
</ul>
<button type="button" id="ekle">Görev ekle</button>const liste = document.getElementById("gorevler");
liste.addEventListener("click", (event) => {
const silDugmesi = event.target.closest("button.sil");
if (!silDugmesi || !liste.contains(silDugmesi)) {
return; // tıklama bir sil düğmesinden gelmedi
}
const satir = silDugmesi.closest("li");
console.log("Silinen görev:", satir.dataset.id);
satir.remove();
});
document.getElementById("ekle").addEventListener("click", () => {
const li = document.createElement("li");
li.dataset.id = String(liste.children.length + 1);
li.innerHTML = '<span class="ad">Yeni görev</span> <button class="sil" type="button">Sil</button>';
liste.append(li); // yeni satır için ayrıca dinleyici gerekmez
});Örnekte dataset okuması yalnızca kimliği taşımak için kullanıldı; data-* öznitelikleri HTML serisinde HTML öğrenme rehberi üzerinden anlatılıyor. Yeni öğe oluşturma ve remove() gibi ağaç işlemleri için DOM eleman oluşturma yazısına, closest() ve diğer gezinme özellikleri için DOM ağacında gezinme yazısına bakabilirsiniz.
Kabarcıklanmayan olaylar
Delegasyon yalnızca kabarcıklanan olaylarla çalışır. event.bubbles özelliği bunu söyler. MDN sayfalarında her olayın kabarcıklanıp kabarcıklanmadığı yazar; örneğin klavye olayları kabarcıklanır ve Document ile Window düzeyine ulaşabilir, buna karşılık load, readystatechange ve formdata olaylarının sayfalarında kabarcıklanmadıkları belirtilir. load için ayrıca ilginç bir kural vardır: load adlı hiçbir olay, bubbles değeri true olsa bile Window nesnesine yayılmaz. Kabarcıklanmayan bir olayı ataların birinde yakalamak gerekiyorsa yakalama evresi bir seçenektir, çünkü yakalama evresi olay kabarcıklansa da kabarcıklanmasa da çalışır.
Shadow DOM sınırında composed ve composedPath
Web bileşenlerinde olayın yolu shadow DOM sınırından da geçebilir. event.composed, olayın shadow ağacından normal DOM'a geçip geçmeyeceğini gösterir. MDN'ye göre tarayıcının gönderdiği tıklama, dokunma, fare üzerine gelme, kopyalama ve yapıştırma gibi kullanıcı arayüzü olaylarının hepsi composed olarak gelir; betikle oluşturulan olaylar ise composed: true verilmedikçe sınırı geçmez. Yayılma için bubbles değerinin de true olması gerekir.
event.composedPath() dinleyicilerin çağrılacağı nesnelerin dizisini döndürür. Shadow kökü mode: "closed" ile oluşturulduysa iç düğümler bu diziye dahil edilmez. MDN örneğinde açık shadow köklü bir öğeye tıklandığında yol p, ShadowRoot, özel öğe, body, html, belge ve Window şeklinde görünürken kapalı köklü öğede yol özel öğenin kendisinden başlar. Delegasyon yaparken shadow içindeki gerçek hedefe ihtiyacınız varsa composedPath() dizisinin ilk elemanına bakabilirsiniz; kapalı köklerde bu mümkün değildir. Özel öğeler ve shadow DOM için Web Components yazısına bakın.
Sık yapılan hatalar

- stopPropagation ile varsayılan eylemi durdurmayı beklemek: Bağlantı yine açılır, form yine gönderilir. Varsayılan eylem için
preventDefault()gerekir. - Her yerde stopPropagation kullanmak: Yayılmayı kesmek, belge düzeyinde delegasyonla çalışan başka kodları (analiz, menü kapatma, kütüphane dinleyicileri) sessizce bozar. Önce olayı gerçekten kesmeniz gerekip gerekmediğini düşünün.
- event.target ile doğrudan eşitlik aramak: Kullanıcı düğme içindeki bir simgeye tıklarsa
targetsimge olur.closest()kullanın. - Özel olayda bubbles vermeyi unutmak: Kendi olaylarınız varsayılan olarak kabarcıklanmaz, ebeveyndeki dinleyici hiçbir şey almaz.
- Yakalama dinleyicisini kaldırırken capture vermemek:
capture: trueile eklenen dinleyici aynı değer verilmeden kaldırılamaz. - Kabarcıklanmayan olayı delegasyonla dinlemek: Olayın MDN sayfasında kabarcıklanıp kabarcıklanmadığını kontrol edin.
Sık Sorulan Sorular
Varsayılan olarak hangi evrede dinlerim?
Kabarcıklanma evresinde. addEventListener() çağrısında capture belirtmezseniz değer false kabul edilir. Dinleyici hedefin kendisindeyse olay hedef evresindedir.
stopPropagation sayfadaki diğer dinleyicileri de durdurur mu?
Yalnızca yayılma yolunda sonra gelecek öğelerdeki dinleyicileri durdurur. Aynı öğede kayıtlı diğer dinleyiciler çalışmaya devam eder; onları da durdurmak için stopImmediatePropagation() gerekir.
Olay delegasyonu performans için her zaman daha mı iyi?
MDN bellek bölümünde tek bir paylaşılan fonksiyonun döngüde üretilen çok sayıda anonim fonksiyondan daha az bellek kullandığını belirtir. Delegasyon kayıt sayısını bire indirir ve dinamik öğeleri kendiliğinden kapsar. Öte yandan Pointer Events belgesindeki en iyi uygulamalar, işaretçi olaylarını belgenin üst düğümleri yerine belirli hedef öğeye eklemeyi önerir; yani olay türüne göre karar verin.
preventDefault çağırdığım olay neden yine de iptal olmadı?
Olay iptal edilebilir değildir (cancelable değeri false), dinleyici pasif olarak eklenmiştir ya da olay betikle cancelable: true verilmeden oluşturulmuştur. event.cancelable ve event.defaultPrevented değerlerini kontrol ederek hangisi olduğunu görebilirsiniz.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Web API öğrenme rehberi · addEventListener kullanımı · CustomEvent ve EventTarget · DOM ağacında gezinme · Web Components ve shadow DOM · Form gönderme ve FormData