Ders 01 / 24
DOM API'si
Belge ağacını okuyan ve değiştiren arayüz; düğüm ile öge ayrımı, canlı koleksiyon ve durağan liste farkı, öznitelik–özellik ikiliği ve toplu değişikliğin maliyeti.
İçindekiler
Web’in Temelleri ve HTML kursu bir belge kurdu: Kuzey Yamaç Ölçüm İstasyonu sayfası, bölümlenmiş bir yapı, ölçüm tablosu ve erişilebilir bir gönderim formu. CSS ile Görsel Sunum kursu o belgeye biçem verdi. İki kurs boyunca sayfa durağandı: sunucudan geldiği biçimiyle duruyor, kullanıcının yaptığı hiçbir şey onu değiştirmiyordu.
Bu kurs sayfaya davranış ekler. Davranışın ilk koşulu, belgeyi bir program içinden okuyabilmek ve değiştirebilmektir. Belge Nesne Modeli Kavramı dersinde ağacın kaynak metinden nasıl kurulduğu gösterilmişti; o derste kurulan yapı bu derste arayüzü olan bir veri yapısı hâline gelir.
Ağaç Bir Nesne Grafiğidir
Ayrıştırıcının ürettiği her düğüm, programın erişebildiği bir nesnedir. Nesneler birbirine ebeveyn, çocuk ve kardeş bağlarıyla bağlıdır; bu bağlar üzerinden ağacın her noktasına ulaşılabilir.
İlk tuzak bu bağların ne taşıdığında saklıdır. Bir ögenin çocukları arasında yalnızca ögeler yoktur: etiketler arasındaki boşluklar da metin düğümü olarak ağaca girer. Bu nedenle API iki ayrı gezinme yolu sunar — biri bütün düğümleri, diğeri yalnızca öge düğümlerini verir.
// dugumler.mjs — cocuk dugumler ile cocuk ogeler arasindaki fark const metin = (deger) => ({ tur: "metin", deger, cocuklar: [] }); const oge = (ad, ozn = {}, ...icerik) => { const dugum = { tur: "oge", ad, ozn, cocuklar: [], ebeveyn: null }; for (const parca of icerik) { const cocuk = typeof parca === "string" ? metin(parca) : parca; cocuk.ebeveyn = dugum; dugum.cocuklar.push(cocuk); } return dugum; }; // K01'de yazilan olcum listesinin agactaki karsiligi: // <ul id="olcumler"> // <li class="olcum">Sicaklik</li> // <li class="olcum">Bagil nem</li> // </ul> const liste = oge("ul", { id: "olcumler" }, "\n ", oge("li", { class: "olcum" }, "Sıcaklık"), "\n ", oge("li", { class: "olcum" }, "Bağıl nem"), "\n"); const cocukDugumler = (d) => d.cocuklar; const cocukOgeler = (d) => d.cocuklar.filter((c) => c.tur === "oge"); console.log("cocuk dugum sayisi :", cocukDugumler(liste).length); console.log("cocuk oge sayisi :", cocukOgeler(liste).length); console.log("ilk cocuk dugum :", JSON.stringify(cocukDugumler(liste)[0].deger)); console.log("ilk cocuk oge :", cocukOgeler(liste)[0].ad);
cocuk dugum sayisi : 5 cocuk oge sayisi : 2 ilk cocuk dugum : "\n " ilk cocuk oge : li
İki li ögesi arasında üç metin düğümü vardır ve üçü de yalnızca satır sonu ile
girintiden oluşur. Tarayıcı API’sinde bu ayrım childNodes ile children,
firstChild ile firstElementChild, nextSibling ile nextElementSibling
çiftleriyle karşılanır. İlk sütundakiler bütün düğümleri, ikinci sütundakiler yalnızca
ögeleri görür.
Bir belgeyi ilk düğümden gezerek dolaşan kod, işaretlemedeki girintiyi değiştirmekle bozulabilir. Bu, kırılganlığın kaynağıdır ve sonraki bölümde ele alınan seçici tabanlı erişimin gerekçesidir.
Düğüm Seçme
Ağacı elle dolaşmak yerine, aranan düğüm bir seçiciyle tarif edilir. CSS ile Görsel Sunum kursundaki Seçiciler dersinde tanımlanan sözdizim burada aynen geçerlidir: biçem kurallarının hangi ögelere uygulanacağını belirleyen dil, programın hangi ögelere erişeceğini de belirler.
İki temel işlem vardır. Biri eşleşen ilk ögeyi döndürür, diğeri eşleşenlerin tamamını. İkisi de bir kökten başlar: belgenin tamamında arama yapılabildiği gibi, belirli bir ögenin alt ağacında da arama yapılabilir. Alt ağaçta arama, aynı seçicinin sayfanın başka bir yerindeki eşleşmelerini dışarıda bırakır ve bu nedenle bileşen sınırları içinde çalışan kodun tercih ettiği biçimdir.
Bir de kimliğe göre erişim vardır. Kimlik belge içinde benzersiz olduğu için bu erişim ağacı taramadan doğrudan bir dizinden okunur; ancak benzersizlik varsayımına dayanır. Aynı kimlik iki kez yazılmışsa ilki döndürülür ve hata verilmez.
Canlı Koleksiyon ve Durağan Liste
Seçme işleminin döndürdüğü şey iki farklı türden olabilir ve aradaki fark, kodun ağaç değiştikten sonra ne göreceğini belirler.
// koleksiyon.mjs — canli koleksiyon ile duragan liste arasindaki fark const oge = (ad, ozn = {}, ...icerik) => { const dugum = { tur: "oge", ad, ozn, cocuklar: [], ebeveyn: null }; for (const parca of icerik) { const cocuk = typeof parca === "string" ? { tur: "metin", deger: parca, cocuklar: [] } : parca; cocuk.ebeveyn = dugum; dugum.cocuklar.push(cocuk); } return dugum; }; // Agactaki tum ogeleri on siralamayla dolasir. function* ogeler(kok) { if (kok.tur === "oge") yield kok; for (const cocuk of kok.cocuklar) yield* ogeler(cocuk); } const esler = (o, secici) => { if (secici.startsWith(".")) return (o.ozn.class ?? "").split(/\s+/).includes(secici.slice(1)); if (secici.startsWith("#")) return o.ozn.id === secici.slice(1); return o.ad === secici; }; // Duragan liste: cagrildigi andaki eslesmeleri diziye kopyalar. const hepsiniSec = (kok, secici) => [...ogeler(kok)].filter((o) => esler(o, secici)); // Canli koleksiyon: her okumada agaci yeniden tarar. const canliKoleksiyon = (kok, secici) => ({ get uzunluk() { return hepsiniSec(kok, secici).length; }, }); const liste = oge("ul", { id: "olcumler" }, oge("li", { class: "olcum" }, "Sıcaklık"), oge("li", { class: "olcum" }, "Bağıl nem")); const duragan = hepsiniSec(liste, "li"); const canli = canliKoleksiyon(liste, "li"); console.log("ekleme oncesi — duragan:", duragan.length, "canli:", canli.uzunluk); const yeni = oge("li", { class: "olcum" }, "Rüzgâr hızı"); yeni.ebeveyn = liste; liste.cocuklar.push(yeni); console.log("ekleme sonrasi — duragan:", duragan.length, "canli:", canli.uzunluk);
ekleme oncesi — duragan: 2 canli: 2 ekleme sonrasi — duragan: 2 canli: 3
Durağan liste çağrıldığı andaki eşleşmelerin bir kopyasıdır; ağaç sonradan değişse de liste değişmez. Canlı koleksiyon ise bir sorgunun kendisidir: her okunduğunda o andaki eşleşmeleri verir.
Tarayıcı API’sinde querySelectorAll durağan, getElementsByTagName ve
getElementsByClassName canlı sonuç döndürür. Ayrım iki durumda önem kazanır. Birincisi,
canlı bir koleksiyon üzerinde döngü kurarken döngünün gövdesinde öge eklemek ya da
silmek koleksiyonun uzunluğunu döngü sürerken değiştirir; sonsuz döngü buradan çıkar.
İkincisi, durağan bir listeyi uzun süre saklamak, ağaçtan çıkarılmış düğümleri bellekte
tutar — bu, Eşzamansız JavaScript ve Çalışma Zamanı kursundaki Bellek Sızıntısı Tanısı
dersinde tanımlanan erişilebilirlik sorununun bu kurstaki karşılığıdır.
Öznitelik ve Özellik
Bir ögenin işaretlemede yazılan href="..." bildirimi ile program tarafındaki href
değeri aynı şey değildir. Birincisi öznitelik (attribute), ikincisi özellik
(property) adını alır. Öznitelik ayrıştırıcının kaynak metinden okuduğu dizgidir;
özellik, o dizgiden türetilmiş ve çalışma sırasında değişebilen değerdir.
// oznitelik.mjs — oznitelik dizgisi ile ozellik degeri arasindaki ayrim const TABAN = "https://ornek.gecerli/istasyon/kuzey-yamac/"; // Oznitelikler her zaman dizgidir; ozellikler cozumlenmis ve yasayan degerlerdir. const OZELLIK = { a: (o) => ({ href: new URL(o.oznitelikler.href, TABAN).href }), "input[text]": (o) => ({ defaultValue: o.oznitelikler.value, // oznitelikten okunur value: o.girilenDeger ?? o.oznitelikler.value, // kullanicinin girdigi deger }), "input[checkbox]": (o) => ({ defaultChecked: "checked" in o.oznitelikler, checked: o.isaretlendi ?? "checked" in o.oznitelikler, }), }; const ogeler = [ ["a", { oznitelikler: { href: "../gunluk?tur=sicaklik" } }], ["input[text]", { oznitelikler: { name: "deger", value: "-4.2" }, girilenDeger: "-6.8" }], ["input[checkbox]", { oznitelikler: { name: "dogrulandi", checked: "" }, isaretlendi: false }], ]; for (const [tur, o] of ogeler) { console.log(tur); console.log(" oznitelik:", JSON.stringify(o.oznitelikler)); console.log(" ozellik :", JSON.stringify(OZELLIK[tur](o))); }
a
oznitelik: {"href":"../gunluk?tur=sicaklik"}
ozellik : {"href":"https://ornek.gecerli/istasyon/gunluk?tur=sicaklik"}
input[text]
oznitelik: {"name":"deger","value":"-4.2"}
ozellik : {"defaultValue":"-4.2","value":"-6.8"}
input[checkbox]
oznitelik: {"name":"dogrulandi","checked":""}
ozellik : {"defaultChecked":true,"checked":false}
Üç ayrışma biçimi görünür. Bağlantıda öznitelik göreli bir adrestir, özellik ise Bağlantılar dersinde tanımlanan taban adrese göre çözümlenmiş mutlak adrestir. Metin alanında öznitelik başlangıç değerini taşır ve kullanıcı yazdıkça değişmez; güncel değer yalnızca özellikte durur. Onay kutusunda öznitelik yalnızca sayfa yüklendiğindeki işaretli olma durumunu bildirir; kullanıcı kutuyu kaldırdığında öznitelik yerinde kalır, özellik yanlış olur.
Buradan pratik bir kural çıkar: kullanıcının değiştirebildiği durum özellikten okunur, belgenin bildirdiği başlangıç durumu öznitelikten. Form alanının o anki değerini öznitelikten okuyan kod, kullanıcının yazdığını hiçbir zaman görmez.
Veri Öznitelikleri dersinde tanımlanan data- öznitelikleri bu ikiliğin dışındadır:
tanımlı bir özellik karşılıkları olmadığı için her iki yönde de dizgi olarak kalırlar.
Düğüm Oluşturma ve Yerleştirme
Yeni içerik iki yolla üretilebilir. Birincisi düğümü tek tek kurmaktır: öge oluşturulur, öznitelikleri yazılır, metin düğümü eklenir, sonra ağaçtaki yerine yerleştirilir. İkincisi bir işaretleme dizgisi verip ayrıştırıcıya ağaç kurdurmaktır.
İkinci yol kısa yazılır ve bir bedeli vardır: dizginin içine kullanıcıdan gelen bir değer
karışırsa, o değerdeki < karakteri etiket olarak yorumlanır. Etiketler, Öznitelikler ve
Varlıklar dersinde tanımlanan kaçırma kuralı burada işletilmediğinde, veri koda dönüşür.
Bu nedenle kullanıcı verisi taşıyan içerik metin düğümü olarak eklenir; metin düğümüne
yazılan < karakteri hiçbir koşulda etiket olmaz.
Yerleştirme işlemlerinin ortak bir özelliği vardır: bir düğümün ağaçta tek bir yeri olabilir. Zaten bağlı bir düğüm başka bir yere eklendiğinde kopyalanmaz, taşınır; eski yerinden kendiliğinden çıkar.
Toplu Değişikliğin Maliyeti
Ağaca bağlı bir düğümü değiştirmek, biçem ve yerleşim hesabının ilgili bölümünü geçersiz kılar. Yüz maddelik bir listeyi tek tek eklemek, bu geçersiz kılmayı yüz kez tetikler. Çözüm, düğümleri belgeye bağlı olmayan bir kapta hazırlamak ve tek seferde bağlamaktır. Bu kabın adı belge parçası (document fragment) dır.
// parca.mjs — tek tek ekleme ile belge parcasiyla ekleme arasindaki fark const oge = (ad, ozn = {}) => ({ tur: "oge", ad, ozn, cocuklar: [], ebeveyn: null }); let gecersizKilma = 0; // belgeye bagli agacta yapilan her degisiklik bunu artirir function bagliMi(dugum) { let ata = dugum; while (ata.ebeveyn) ata = ata.ebeveyn; return ata.tur === "belge"; } // Bir cagri = bir agac degisikligi. function ekle(hedef, ...dugumler) { for (const d of dugumler) { d.ebeveyn = hedef; hedef.cocuklar.push(d); } if (bagliMi(hedef)) gecersizKilma += 1; } const belge = { tur: "belge", ad: "#belge", cocuklar: [], ebeveyn: null }; const liste = oge("ul", { id: "olcumler" }); ekle(belge, liste); const kayitlar = ["Sıcaklık", "Bağıl nem", "Rüzgâr hızı", "Yağış", "Basınç"]; // Birinci yol: her madde ayri bir cagriyla bagli agaca eklenir. gecersizKilma = 0; for (const ad of kayitlar) ekle(liste, oge("li", { "data-olcum": ad })); console.log("dogrudan ekleme:", gecersizKilma, "gecersiz kilma,", liste.cocuklar.length, "cocuk"); // Ikinci yol: maddeler once bagli olmayan parcada toplanir, sonra tek cagriyla eklenir. liste.cocuklar.length = 0; gecersizKilma = 0; const parca = { tur: "parca", ad: "#parca", cocuklar: [], ebeveyn: null }; for (const ad of kayitlar) ekle(parca, oge("li", { "data-olcum": ad })); ekle(liste, ...parca.cocuklar.splice(0)); console.log("parcayla ekleme:", gecersizKilma, "gecersiz kilma,", liste.cocuklar.length, "cocuk"); console.log("parcada kalan :", parca.cocuklar.length, "cocuk");
dogrudan ekleme: 5 gecersiz kilma, 5 cocuk parcayla ekleme: 1 gecersiz kilma, 5 cocuk parcada kalan : 0 cocuk
Belge parçasının iki özelliği çıktıda görünür. Birincisi, parçaya yapılan eklemeler belgeye bağlı olmadığı için geçersiz kılma üretmez. İkincisi, parça ağaca eklendiğinde kendisi ağaca girmez; yalnızca çocuklarını devreder ve boşalır. Son satırdaki sıfır bunu gösterir.
Buradaki sayı bir başarım ölçümü değil, bir çağrı sayımıdır. Gerçek maliyetin ne kadar olduğu belgeye ve biçeme bağlıdır; ölçülmesi Başarım Kaydı dersinin konusudur. Sabit olan şey, tetiklenen hesap sayısının beşten bire indiğidir.
Özet
- Belge ağacı, düğümleri ebeveyn–çocuk–kardeş bağlarıyla bağlı bir nesne grafiğidir; bu bağların bir bölümü bütün düğümleri, bir bölümü yalnızca ögeleri görür.
- Etiketler arasındaki girinti ve satır sonları metin düğümü olarak ağaca girer; düğüm bağlarıyla gezinen kod işaretlemenin biçimlendirilmesine duyarlıdır.
- Seçme işlemi ya durağan bir liste ya da canlı bir koleksiyon döndürür; canlı koleksiyon her okunduğunda ağacı yeniden sorgular.
- Öznitelik kaynak metindeki dizgidir, özellik ondan türetilmiş yaşayan değerdir; kullanıcı girdisi yalnızca özellikte görünür.
- Bağlı bir düğüme yapılan her değişiklik biçem ve yerleşim hesabını geçersiz kılar; toplu ekleme bağlı olmayan bir belge parçasında hazırlanır.
Sonraki Adım
Bu ders belgeyi okumayı ve değiştirmeyi kurdu, ancak değişikliği neyin tetikleyeceğini açık bıraktı. Bir sayfa kendiliğinden değişmez; kullanıcının tıkladığı, yazdığı, kaydırdığı an bir bildirim üretir ve program o bildirime yanıt verir. Bu bildirimlerin ağaç üzerinde izlediği yol tanımlıdır ve tek bir düğümden ibaret değildir: olay, hedefe ulaşmadan önce atalarından geçer ve hedeften sonra aynı yoldan geri döner. Sonraki ders bu üç evreli yolculuğu ve dinleyicilerin hangi sırayla çağrıldığını inceler.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.