Ders 03 / 24
Olay Devri
Ata düğüme bağlanan tek dinleyiciyle çok sayıda ögenin olaylarını karşılamak; hedefi geçerli hedeften ayırt etme, en yakın eşleşen atayı bulma, kimliği işaretlemede taşıma ve devrin sınırları.
İçindekiler
Önceki ders olayın hedefe ulaşmadan önce ve ulaştıktan sonra atalarından geçtiğini gösterdi. Bu yolculuk bir yük değil bir olanaktır: bir atanın dinleyicisi, o atanın altındaki bütün ögelerin olaylarını görür. Ölçüm tablosunun her satırına ayrı dinleyici bağlamak yerine tabloya tek dinleyici bağlanabilir.
Bu tekniğin adı olay devri (event delegation) dir. Devrin işe yaraması tek bir ayrıma dayanır: dinleyici ata düğüme bağlıdır, ama olayı üreten öge hedeftir. Dinleyici, kendisine gelen olayın hangi alt ögeden geldiğini çözümlemek zorundadır.
İki Sorun
Doğrudan bağlama iki ayrı sorun üretir ve ikincisi daha sinsidir.
Birincisi kayıt sayısıdır. Yüz satırlık bir tabloda satır başına bir dinleyici, yüz kayıt demektir. Her kayıt bellekte bir giriş tutar ve her kayıt, ögenin ağaçtan çıkarılmasıyla temizlenmesi gereken bir bağ kurar.
İkincisi yaşam süresidir. Dinleyici bir ögeye kaydedildiği anda var olan ögeye bağlanır. Sayfa yüklendikten sonra eklenen bir satır, kayıt sırasında ortada olmadığı için dinleyicisiz kalır. Bu, DOM API’si dersindeki canlı koleksiyon ile durağan liste ayrımının davranış tarafındaki karşılığıdır: durağan bir anda kurulan bağ, sonradan gelen düğümleri kapsamaz.
Devir iki sorunu birden çözer. Dinleyici, satırlardan daha uzun yaşayan bir ataya — tabloya — bağlanır. Satırlar gelip gitse de tablo yerinde durur.
Hedefi Çözümlemek
Devredilen dinleyicinin ilk işi, gelen olayın ilgilendiği bir ögeden gelip gelmediğine karar vermektir. Burada iki işlem birbirine karıştırılır.
Eşleşme sınaması bir ögenin verilen seçiciye uyup uymadığını söyler; yalnız o ögeye bakar. En yakın ata araması hedeften başlar, ata zincirini yukarı yürür ve seçiciye uyan ilk düğümü döndürür.
Fark, tıklamanın nereye düştüğünde ortaya çıkar. Silme düğmesinin içinde bir simge ögesi varsa, kullanıcı düğmeye bastığında hedef düğme değil simgedir. Yalnız hedefi sınayan kod bu tıklamayı kaçırır.
// enyakin.mjs — hedeften sinira dogru yuruyen es arama const oge = (ad, ozn = {}, ...cocuklar) => { const d = { ad, ozn, cocuklar, ebeveyn: null }; for (const c of cocuklar) c.ebeveyn = d; return d; }; const adlandir = (d) => d.ad + (d.ozn.id ? "#" + d.ozn.id : "") + (d.ozn.class ? "." + d.ozn.class : ""); const esler = (d, secici) => { if (secici.startsWith(".")) return (d.ozn.class ?? "").split(/\s+/).includes(secici.slice(1)); if (secici.startsWith("#")) return d.ozn.id === secici.slice(1); return d.ad === secici; }; // Hedeften baslar, sinira kadar yukari yurur; sinir da denenir, otesi denenmez. const enYakin = (hedef, secici, sinir) => { for (let d = hedef; d; d = d.ebeveyn) { if (esler(d, secici)) return d; if (d === sinir) break; } return null; }; const simge = oge("span", { class: "simge" }); const dugme = oge("button", { class: "sil" }, simge); const satir = oge("tr", { class: "olcum", "data-kod": "T-04" }, oge("td", {}), oge("td", {}, dugme)); const tablo = oge("table", { id: "gunluk" }, satir); console.log("hedef :", adlandir(simge)); console.log("gecerli hedef :", adlandir(tablo)); console.log("esler(hedef, '.sil') :", esler(simge, ".sil")); console.log("enYakin('.sil') :", adlandir(enYakin(simge, ".sil", tablo))); console.log("enYakin('tr') :", adlandir(enYakin(simge, "tr", tablo)), enYakin(simge, "tr", tablo).ozn["data-kod"]); console.log("enYakin('body') :", enYakin(simge, "body", tablo));
hedef : span.simge
gecerli hedef : table#gunluk
esler(hedef, '.sil') : false
enYakin('.sil') : button.sil
enYakin('tr') : tr.olcum T-04
enYakin('body') : null
Üçüncü satır sorunu gösterir: hedefin kendisi silme düğmesi değildir. Dördüncü satır çözümü gösterir: yukarı yürüyen arama düğmeyi bulur. Beşinci satır bir adım daha gider ve düğmenin ait olduğu satırı bulur; ölçümün kodu oradan okunur.
Son satır sınır kavramını gösterir. Arama geçerli hedefte durur. Bu sınır olmadan
arama belgenin köküne kadar çıkar ve dinleyicinin kapsamı dışındaki bir düğümü döndürerek
başka bir bileşenin ögesi üzerinde işlem yapılmasına yol açabilir. Tarayıcı API’sinde bu
işlemlerin adı matches ve closest tir; closest sınır almadığı için sonucun geçerli
hedefin altında kaldığı ayrıca sınanır.
Devri Kurmak
İki yaklaşımın davranış farkı, sonradan eklenen bir satırda görünür.
// devir.mjs — her ogeye baglama ile tek dinleyiciyle devir arasindaki fark const oge = (ad, ozn = {}, ...cocuklar) => { const d = { ad, ozn, cocuklar, ebeveyn: null, dinleyiciler: [] }; for (const c of cocuklar) c.ebeveyn = d; return d; }; const esler = (d, s) => s.startsWith(".") ? (d.ozn.class ?? "").split(/\s+/).includes(s.slice(1)) : d.ad === s; const enYakin = (hedef, s, sinir) => { for (let d = hedef; d; d = d.ebeveyn) { if (esler(d, s)) return d; if (d === sinir) break; } return null; }; // Kabarcik evresi: hedeften koke dogru her dugumun dinleyicileri cagrilir. const tikla = (hedef) => { for (let d = hedef; d; d = d.ebeveyn) for (const dinleyici of d.dinleyiciler) dinleyici({ hedef, gecerliHedef: d }); }; const dinleyiciSayisi = (kok) => kok.dinleyiciler.length + kok.cocuklar.reduce((t, c) => t + dinleyiciSayisi(c), 0); const satirKur = (kod) => oge("tr", { class: "olcum", "data-kod": kod }, oge("td", {}), oge("td", {}, oge("button", { class: "sil" }))); const tabloKur = () => oge("table", { id: "gunluk" }, oge("tr", { class: "baslik" }, oge("th", {})), satirKur("T-01"), satirKur("T-02"), satirKur("T-03")); const dugmeler = (kok) => [kok, ...kok.cocuklar.flatMap((c) => dugmeler(c))].filter((d) => esler(d, ".sil")); let silinen = []; const sil = (satir) => silinen.push(satir.ozn["data-kod"]); // Birinci yol: her silme dugmesine ayri dinleyici. const a = tabloKur(); for (const b of dugmeler(a)) b.dinleyiciler.push(({ gecerliHedef }) => sil(gecerliHedef.ebeveyn.ebeveyn)); console.log("dogrudan baglama, dinleyici:", dinleyiciSayisi(a)); a.cocuklar.push(Object.assign(satirKur("T-04"), { ebeveyn: a })); // sonradan gelen satir silinen = []; tikla(dugmeler(a)[0]); console.log(" T-01 dugmesi:", silinen.length ? silinen : "islem yok"); silinen = []; tikla(dugmeler(a)[3]); console.log(" T-04 dugmesi:", silinen.length ? silinen : "islem yok"); // Ikinci yol: tabloya tek dinleyici; hedef her cagrida yeniden cozumlenir. const b = tabloKur(); b.dinleyiciler.push(({ hedef, gecerliHedef }) => { const dugme = enYakin(hedef, ".sil", gecerliHedef); if (dugme === null) return; sil(enYakin(dugme, "tr", gecerliHedef)); }); console.log("devir, dinleyici :", dinleyiciSayisi(b)); b.cocuklar.push(Object.assign(satirKur("T-04"), { ebeveyn: b })); silinen = []; tikla(dugmeler(b)[0]); console.log(" T-01 dugmesi:", silinen.length ? silinen : "islem yok"); silinen = []; tikla(dugmeler(b)[3]); console.log(" T-04 dugmesi:", silinen.length ? silinen : "islem yok"); silinen = []; tikla(b.cocuklar[0].cocuklar[0]); console.log(" baslik hucresi:", silinen.length ? silinen : "islem yok");
dogrudan baglama, dinleyici: 3 T-01 dugmesi: [ 'T-01' ] T-04 dugmesi: islem yok devir, dinleyici : 1 T-01 dugmesi: [ 'T-01' ] T-04 dugmesi: [ 'T-04' ] baslik hucresi: islem yok
Doğrudan bağlamada dinleyici sayısı satır sayısıyla birlikte artar ve sonradan eklenen satır sessiz kalır. Devirde tek kayıt vardır ve sonradan eklenen satır ilk satırdan farksız çalışır. Farkın kaynağı zamanlamadır: doğrudan bağlamada eşleşme kayıt anında, devirde olay anında kurulur.
Son satır devrin ikinci gereğini gösterir. Tabloya bağlı dinleyici, tablonun altındaki her tıklamayı görür — başlık hücresini, boş bir hücreyi, satır arasındaki boşluğu. Eşleşme bulunamadığında dinleyici hiçbir şey yapmadan döner. Bu erken çıkış, devredilen her dinleyicinin ilk satırıdır.
Kimliği İşaretlemede Taşımak
Devredilen dinleyici, olayın hangi kayda ait olduğunu bilmek zorundadır. Bu bilgiyi bir dizide sıra numarasıyla tutmak kırılgandır: satırlar sıralandığında ya da süzüldüğünde sıra numarası kayar.
Sağlam yol, kimliği ögenin kendisinde taşımaktır. Web’in Temelleri ve HTML kursundaki
Veri Öznitelikleri dersinde tanımlanan data- öznitelikleri bu iş için vardır: değer
işaretlemede durur, öge ağaçta hareket etse de değeriyle birlikte hareket eder. Yukarıdaki
örnekte satırın ölçüm kodu bu yolla okunmuştur.
Aynı öznitelikler eylem türünü de taşıyabilir. Bir satırda silme, düzenleme ve işaretleme düğmeleri varsa, her biri için ayrı bir seçici sınamak yerine düğmelerin ortak bir özniteliğinde eylem adı tutulur; dinleyici tek arama yapar ve eylemi bir eşlemeden çağırır. Bu, dinleyicinin uzunluğunu satır sayısından da eylem sayısından da bağımsız kılar.
Devrin Sınırları
Devir her olay türünde işlemez ve iki sınırı vardır.
Kabarmayan olaylar devredilemez. Önceki derste tanımlandığı gibi odak alma ve odak kaybetme olayları kabarmaz; ataya bağlı kabarcık kipindeki bir dinleyici onları görmez. Bu olayların kabaran karşılıkları ayrı türler olarak tanımlıdır ve devir onlarla kurulur. İkinci yol, dinleyiciyi ataya yakalama kipinde kaydetmektir: yakalama evresi kabarmayan olaylarda da işlediği için ata düğüm olayı görür. İki yolun seçimi, olayın hedefe ulaşmadan mı ulaştıktan sonra mı işlenmesi gerektiğine bağlıdır.
Yayılımı durduran ara dinleyiciler devri sessizce bozar. Tabloya devredilmiş bir
dinleyici, satırın içindeki bir dinleyici stopPropagation çağırdığında hiç
çağrılmaz. Önceki derste yayılımı durdurmanın son çare olduğu söylenmişti; devir
kurulmuş bir ağaçta bu, uzaktan etki eden bir kesintiye dönüşür.
Bir üçüncü nokta sınır değil ama tasarım kararıdır: dinleyicinin bağlandığı ata ne kadar yukarıdaysa kapsamı o kadar geniştir. Belgenin tamamına bağlanan bir dinleyici sayfadaki her tıklamada çalışır. Devir, ilgilenilen ögelerin ortak en yakın atasına kurulur; tablonun olayları tabloya, formun olayları forma devredilir.
Özet
- Olay devri, ilgilenilen ögelerin bir atasına tek dinleyici bağlayıp hedefi olay anında çözümlemektir; dinleyici sayısını öge sayısından bağımsız kılar.
- Devir, kayıt anında var olmayan ögeleri de kapsar; eşleşme kayıt anında değil olay anında kurulduğu için sonradan eklenen düğümler dinleyicisiz kalmaz.
- Hedef, seçiciye uyan en yakın ataya çözümlenir; yalnız hedefi sınayan kod, iç içe ögelerden gelen tıklamaları kaçırır.
- Arama geçerli hedefte durdurulur; sınırsız arama dinleyicinin kapsamı dışındaki bir düğümü döndürebilir.
- Kayıt kimliği
data-öznitelikleriyle ögenin kendisinde taşınır; sıra numarasına dayanan eşleme sıralama ve süzme sonrasında bozulur. - Kabarmayan olaylar kabarcık kipiyle devredilemez; yakalama kipi ya da kabaran karşılık türü kullanılır. Ara bir dinleyicinin yayılımı durdurması devri sessizce iptal eder.
Sonraki Adım
Bu derse kadar dinleyiciler yalnızca eklediler: olay geldi, kod çalıştı, sayfaya bir davranış katıldı. Oysa tarayıcının kendi davranışları da vardır ve bunlar dinleyici olmadan da işler. Bağlantıya tıklamak gezinme başlatır, gönderim düğmesi formu gönderir, onay kutusu işaretini değiştirir, boşluk tuşu sayfayı kaydırır. Bir dinleyici bu davranışların yerine kendi davranışını koymak istediğinde, tarayıcının yapacağı işi iptal etmesi gerekir. Sonraki ders bu iptalin nasıl istendiğini, hangi olaylarda mümkün olduğunu ve yayılımı durdurmakla neden karıştırılmaması gerektiğini inceler.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.