Ders 04 / 26
Birleştirici Seçiciler
Alt öge, çocuk, bitişik kardeş ve genel kardeş birleştiricileri; bir seçicinin sağdan sola değerlendirilmesi ve anahtar seçici kavramı.
İçindekiler
Önceki dersin son kuralında .kunye .ozet yazılmıştı ve iki seçici arasındaki boşluğun bir
işleç olduğu söylenip geçilmişti. O boşluk birleştirici (combinator) adını taşır ve
seçiciye ağaçtaki konum sorusunu ekler: ögenin kendisi değil, nerede durduğu sorulur.
Bu ders dört birleştiriciyi tanımlar ve bir seçicinin nasıl değerlendirildiğini gösterir. Değerlendirme sırası, hem sonucu hem de bir seçicinin maliyetini anlamak için gereklidir.
Dört Birleştirici
| Yazım | Ad | Sorduğu soru |
|---|---|---|
A B |
alt öge birleştiricisi (descendant) | B’nin atalarından biri A mı? |
A > B |
çocuk birleştiricisi (child) | B’nin doğrudan üstü A mı? |
A + B |
bitişik kardeş (adjacent sibling) | B’nin bir önceki kardeşi A mı? |
A ~ B |
genel kardeş (general sibling) | B’nin önceki kardeşlerinden biri A mı? |
Dördü de aynı biçimde okunur: sağdaki seçilir, soldaki koşuldur. .olcum-cizelgesi td
kuralı td ögelerini seçer; .olcum-cizelgesi yalnızca hangi td ögelerinin seçileceğini
daraltır. Sol taraf hiçbir zaman biçimlendirilmez.
Kardeş birleştiricileri yalnızca geriye bakar. A + B yazımı “A’dan sonra gelen B”
anlamındadır; “B’den sonra gelen A” diye bir birleştirici yoktur.
Değerlendirme Sağdan Sola Yapılır
Bir seçici soldan sağa okunur ama sağdan sola değerlendirilir. Sebep, iki yönün maliyet farkındadır.
Soldan sağa gidilseydi .olcum-cizelgesi td için önce bütün belgede o sınıfı taşıyan
ögeler bulunur, sonra her birinin bütün alt ağacı taranırdı. Sağdan sola gidildiğinde
ise her td ögesi için yalnızca atalar zinciri yukarı doğru izlenir; zincirin uzunluğu
ağacın derinliği kadardır. İkinci yol, bir ögenin biçemi hesaplanırken sorulan sorunun da
doğal yönüdür: “bu ögeye hangi kurallar uyuyor?”
Sağdaki bileşik seçiciye anahtar seçici (key selector) denir. Bir kuralın hangi ögeler için sınanacağını belirleyen odur.
Aşağıdaki program dört birleştiriciyi sağdan sola değerlendirir. Ağaç, ölçüm çizelgesinin
gerçek yapısını taşır: satırlar thead ve tbody içinde gruplanmıştır.
// birlestirici.mjs — dort birlestiriciyi sagdan sola degerlendirir const agac = { ad: "body", sinif: [], cocuk: [ { ad: "main", sinif: [], cocuk: [ { ad: "table", sinif: ["olcum-cizelgesi"], cocuk: [ { ad: "thead", sinif: [], cocuk: [ { ad: "tr", sinif: [], cocuk: [{ ad: "th", sinif: [], cocuk: [] }] }, ]}, { ad: "tbody", sinif: [], cocuk: [ { ad: "tr", sinif: [], cocuk: [ { ad: "td", sinif: ["ad"], cocuk: [] }, { ad: "td", sinif: ["deger"], cocuk: [] }, { ad: "td", sinif: ["deger", "eksik"], cocuk: [] }, ]}, ]}, ]}, { ad: "p", sinif: ["not"], cocuk: [] }, { ad: "table", sinif: [], cocuk: [ { ad: "tbody", sinif: [], cocuk: [ { ad: "tr", sinif: [], cocuk: [{ ad: "td", sinif: ["deger"], cocuk: [] }] }, ]}, ]}, ]}, ]}; // her dugume ust ve kardes bilgisi ekle function bagla(d, ust = null) { d.ust = ust; d.cocuk.forEach((c, i) => { c.oncekiKardes = i > 0 ? d.cocuk[i - 1] : null; bagla(c, d); }); return d; } bagla(agac); const yol = (d) => (d.ust ? yol(d.ust) + " " : "") + d.ad + d.sinif.map((s) => "." + s).join(""); function* hepsi(d) { yield d; for (const c of d.cocuk) yield* hepsi(c); } const basitEsler = (s, d) => s === "*" ? true : s.startsWith(".") ? d.sinif.includes(s.slice(1)) : d.ad === s; // bilesik secici: bosluksuz basit seciciler function bilesikEsler(bilesik, d) { const parcalar = bilesik.match(/(^[a-z]+|\.[a-z-]+|\*)/g) ?? []; return parcalar.every((p) => basitEsler(p, d)); } // secici: bilesik (birlestirici bilesik)* — sagdan sola cozulur function secERler(secici, dugum) { const jetonlar = secici.trim().split(/\s+/); let d = dugum; let i = jetonlar.length - 1; if (!bilesikEsler(jetonlar[i], d)) return false; i--; while (i >= 0) { const birlestirici = ">+~".includes(jetonlar[i]) ? jetonlar[i--] : " "; const hedef = jetonlar[i--]; if (birlestirici === ">") { d = d.ust; if (!d || !bilesikEsler(hedef, d)) return false; } else if (birlestirici === "+") { d = d.oncekiKardes; if (!d || !bilesikEsler(hedef, d)) return false; } else if (birlestirici === "~") { let k = d.oncekiKardes; while (k && !bilesikEsler(hedef, k)) k = k.oncekiKardes; if (!k) return false; d = k; } else { let a = d.ust; while (a && !bilesikEsler(hedef, a)) a = a.ust; if (!a) return false; d = a; } } return true; } const denemeler = [ "table td", ".olcum-cizelgesi td", ".olcum-cizelgesi > td", "tbody > tr > td", "td.ad + td", "td.ad ~ td", ".olcum-cizelgesi + p", ]; for (const s of denemeler) { const bulunan = [...hepsi(agac)].filter((d) => secERler(s, d)); console.log(`${s.padEnd(26)} -> ${bulunan.length}`); for (const b of bulunan) console.log(` ${yol(b)}`); }
table td -> 4 body main table.olcum-cizelgesi tbody tr td.ad body main table.olcum-cizelgesi tbody tr td.deger body main table.olcum-cizelgesi tbody tr td.deger.eksik body main table tbody tr td.deger .olcum-cizelgesi td -> 3 body main table.olcum-cizelgesi tbody tr td.ad body main table.olcum-cizelgesi tbody tr td.deger body main table.olcum-cizelgesi tbody tr td.deger.eksik .olcum-cizelgesi > td -> 0 tbody > tr > td -> 4 body main table.olcum-cizelgesi tbody tr td.ad body main table.olcum-cizelgesi tbody tr td.deger body main table.olcum-cizelgesi tbody tr td.deger.eksik body main table tbody tr td.deger td.ad + td -> 1 body main table.olcum-cizelgesi tbody tr td.deger td.ad ~ td -> 2 body main table.olcum-cizelgesi tbody tr td.deger body main table.olcum-cizelgesi tbody tr td.deger.eksik .olcum-cizelgesi + p -> 1 body main p.not
Bu gerçekleştirim, alt öge ve genel kardeş adımlarında geriye dönüş yapmaz: eşleşen ilk atayı ya da ilk kardeşi seçer ve orada kalır. Gerçek bir eşleyici, sonraki adım başarısızsa geri döner ve başka bir ata dener. Yukarıdaki yedi örnekte iki davranış aynı sonucu verir.
Sıfır Öge Döndüren Seçici
Üçüncü satır dikkat ister: .olcum-cizelgesi > td hiçbir öge döndürmedi, oysa
.olcum-cizelgesi td üç öge döndürdü. Aradaki tek fark bir işarettir.
Sebep ağaçtadır. Hücreler tablonun doğrudan çocuğu değildir; aradaki zincir
table > tbody > tr > td biçimindedir. Çocuk birleştiricisi tam bir adım ister ve o adım
tbody ögesine denk gelir.
Web’in Temelleri ve HTML kursunda ayrıştırıcının kaynakta yazılmamış ögeleri kurduğu
anlatılmıştı; tbody bunların en sık karşılaşılanıdır. Kaynak metinde tbody etiketi
yazılmamış olsa bile öge belgede vardır ve seçici o ögeyi görür. Bu, “etiket” ile “öge”
ayrımının biçem tarafındaki ilk somut sonucudur: seçici kaynak metne değil, ayrıştırma
sonrası ağaca bakar.
Çocuk Birleştiricisi Ne Zaman Gerekir
Alt öge birleştiricisi derinlik gözetmez ve bu çoğu zaman istenen davranıştır. Ancak iç içe geçmiş aynı türden yapılarda istenmeyen sonuç verir.
İstasyon sayfasının gezinme bölümü iç içe listeler taşıyorsa, .gezinme li seçicisi hem
üst düzey maddeleri hem alt maddeleri döndürür. Yalnızca üst düzeyi hedeflemek için
.gezinme > ul > li yazılır. Kural şudur: derinlik anlam taşıyorsa çocuk birleştiricisi,
taşımıyorsa alt öge birleştiricisi kullanılır.
Kardeş Birleştiricileri Sıraya Bağlıdır
td.ad + td bir hücre döndürdü: ad hücresinden hemen sonraki değer hücresi. td.ad ~ td
ise ikisini birden döndürdü, çünkü genel kardeş birleştiricisi araya kaç kardeş girdiğine
bakmaz.
Kardeş birleştiricilerinin tipik kullanımı, iki ögenin arasındaki ilişkiyi biçimlendirmektir. Ard arda gelen iki paragraf arasına boşluk koymak için:
p + p { margin-block-start: 0.75em; }
Bu yazım, bir bölümün ilk paragrafına boşluk eklemez — çünkü onun önünde bir paragraf kardeş yoktur. Aynı sonucu her paragrafa boşluk verip ilkini geri almakla da elde etmek mümkündür; kardeş birleştiricisi ile yazılan biçim, kuralı tek bir yerde ve istisnasız tutar.
İstasyon Sayfasının Kuralları
Biçem dosyası bu dersle birlikte ağaçtaki konumları kullanmaya başlar:
/* istasyon.css — 3. adım: ilişkiye bağlı kurallar */ .olcum-cizelgesi tbody td { padding: 8px 12px; } .olcum-cizelgesi tbody > tr { border-block-end: 1px solid #d5dbe0; } .gezinme > ul > li { display: inline-block; } .olcum-cizelgesi + p { font-size: 0.875rem; }
Son kural, çizelgeden hemen sonra gelen açıklama paragrafını küçültür. Paragrafa ayrı bir sınıf vermek de aynı sonucu verirdi; aradaki fark, kuralın belgeye bir sınıf ekleme gerektirip gerektirmemesidir. Belgeye dokunmadan çözülebilen bir biçem sorunu, biçem dosyasında çözülür.
Özet
- Birleştirici, seçiciye ağaçtaki konum sorusunu ekler; dördü de aynı biçimde okunur — sağdaki seçilir, soldaki koşuldur.
- Alt öge birleştiricisi derinlik gözetmez, çocuk birleştiricisi tam bir adım ister; bitişik kardeş bir önceki kardeşe, genel kardeş önceki kardeşlerin herhangi birine bakar.
- Seçici sağdan sola değerlendirilir; en sağdaki bileşik seçici anahtar seçicidir ve kuralın hangi ögeler için sınanacağını belirler.
- Seçici, kaynak metne değil ayrıştırma sonrası ağaca bakar; kaynakta yazılmayan
tbodygibi ögeler zincire dâhildir ve çocuk birleştiricisini etkiler. - Kardeş birleştiricileri yalnızca geriye bakar; “sonraki kardeşin öncesi” diye bir birleştirici yoktur.
Sonraki Adım
Seçiciler şimdiye kadar öge adına, sınıfa, kimliğe ve ağaçtaki konuma baktı. Belgede bir bilgi daha var ve henüz kullanılmadı: öznitelik değerleri. Form alanlarının türü, bir bağlantının hedef adresi, veri öznitelikleriyle iliştirilen ölçüm birimleri — hepsi özniteliklerde durur. Sonraki ders öznitelik seçicilerini ve değer eşleme biçimlerini tanımlar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.