Autor del tema
#0
DOM Traversing Teknikleri
Web uygulamaları geliştirirken, JavaScript ile dinamik olarak içeriği manipüle etmek, kullanıcı etkileşimlerine yanıt vermek veya belirli bir arayüz durumunu güncellemek sıklıkla karşımıza çıkan bir ihtiyaç. HTML belgesini bir ağaç yapısı olarak düşündüğümüzde, bu ağacın dalları ve yaprakları arasında gezintiye çıkmak, yani bir elementten yola çıkarak diğer elementlere ulaşmak, işte tam da bu "DOM Traversing" kavramının temelini oluşturuyor. Birçok geliştirici, özellikle yeni başlayanlar, bu konuda bazen zorlanabiliyor veya en verimli yöntemi bulmakta güçlük çekebiliyor. Belirli bir düğümden yola çıkarak ebeveynine, çocuklarına veya kardeşlerine ulaşmak, göründüğünden çok daha fazla ince detayı barındırıyor...
Bir elementin ebeveynine ulaşmak, genellikle bir olayın tetiklendiği elementi bulduktan sonra, bu elementin içinde bulunduğu daha geniş kapsayıcıya erişmek istediğimizde kritik hale gelir. Örneğin, bir liste öğesine (LI) tıklandığında, tüm listeyi (UL) etkilemek isteyebiliriz. Bu noktada kullanabileceğimiz çeşitli özellikler var. parentNode özelliği, bir düğümün doğrudan ebeveyn düğümünü döndürür. Bu, herhangi bir düğüm türü olabilir; bir element düğümü, metin düğümü veya yorum düğümü... Yani, boşluk karakterleri veya satır sonları bile birer metin düğümü olarak sayılabilir. parentElement ise sadece element düğümlerini döndürür; eğer ebeveyn bir element düğümü değilse (örneğin belgenin kendisi), null döner. Bu ayrım, özellikle tarayıcıların boşlukları nasıl ele aldığına bağlı olarak beklenmedik sonuçlar doğurabileceği için oldukça önemli. Deneyimlerime göre, çoğunlukla bir elementin ebeveyn elementine ihtiyacımız olduğundan, parentElement çoğu senaryoda daha güvenilir bir seçim olacaktır.
Eğer daha yukarılarda, belirli bir seçiciye uyan ilk ebeveyn elementi arıyorsak, closest() metodu adeta bir kurtarıcı gibidir. Bu metot, kendisine verilen CSS seçiciye uyan en yakın ebeveyn elementini (kendi dahil) bulana kadar DOM ağacında yukarı doğru arama yapar. Bu, özellikle iç içe geçmiş çok sayıda elementle çalışırken ve belirli bir "kapsayıcıya" ulaşmak istediğimizde hayati önem taşır. Örneğin, bir ürün kartının içindeki "Sepete Ekle" butonuna tıkladığımızda, tüm ürün kartının verilerine veya ID'sine ulaşmak isteyebiliriz. closest('.product-card') çağrısı, bu karmaşık yapıda bile hedefe şaşırmadan ulaşmamızı sağlar. Bu metot, performansı da düşünerek tasarlanmıştır; gereksiz yere tüm DOM ağacını taramaz, sadece ihtiyacı olan yere kadar çıkar.
DOM ağacında aşağı doğru inmek, yani bir elementin çocuklarına ulaşmak, belki de en sık karşılaşılan travers etme senaryolarından biridir. Bir listeyi dinamik olarak doldurmak, bir tablonun satırlarına erişmek veya bir formun içindeki alanları kontrol etmek... Tüm bunlar için çocuk elementlere erişim şart. Burada da childNodes ve children arasındaki farkı iyi anlamak gerekiyor. childNodes, bir elementin tüm çocuk düğümlerini içeren bir NodeList döndürür; bu liste element düğümlerini, metin düğümlerini (boşluklar ve satır sonları dahil) ve yorum düğümlerini içerebilir. Dolayısıyla, HTML kodunuzdaki her bir satır sonu veya girinti bile bu liste içinde bir düğüm olarak yerini alabilir. Bu durum, özellikle belirli bir çocuk elemente index ile erişmeye çalıştığınızda beklenmedik sonuçlara yol açabilir.
Öte yandan, children özelliği ise yalnızca element düğümlerini içeren bir HTMLCollection döndürür. Bu, genellikle aradığımız şeydir, çünkü çoğu zaman metin düğümlerini veya yorumları değil, görsel olarak sayfada yer alan diğer elementleri hedef alırız. Bu nedenle, genellikle children kullanmak, kodunuzu daha öngörülebilir ve hataya daha az açık hale getirecektir. Aynı mantık, firstChild ve firstElementChild ile lastChild ve lastElementChild için de geçerlidir.
Bir elementin kardeş elementlerine (sibling elements) ulaşmak da benzer bir Node/Element ayrımıyla gelir. nextSibling ve previousSibling özellikleri, sırasıyla bir elementin hemen sonraki veya hemen önceki kardeş düğümünü döndürür. Tıpkı çocuk düğümlerinde olduğu gibi, bunlar da element, metin veya yorum düğümleri olabilir. Yine, boşluklar ve satır sonları burada da karşımıza çıkabilir. Metin düğümlerinin araya girmesi nedeniyle, bir sonraki elemente ulaşmak için bazen birden fazla nextSibling çağrısı yapmamız gerekebilir, ki bu da pek pratik veya güvenilir bir yöntem değildir.
İşte bu noktada nextElementSibling ve previousElementSibling devreye girer. Bu özellikler, bize doğrudan bir sonraki veya bir önceki kardeş elementini döndürür. Yani, aradaki metin düğümlerini veya yorumları tamamen göz ardı ederler. Bu sayede, kodumuz çok daha temiz, okunabilir ve beklenmedik durumlarla karşılaşma olasılığı daha düşük olur. Özellikle dinamik olarak sıralanabilir listeler veya bir formun alanları arasında gezinme gibi senaryolarda bu özellikler paha biçilmezdir.
Peki, tüm bu manuel travers tekniklerinin yanı sıra, daha genel yaklaşımlar yok mu? Elbette var. querySelector() ve querySelectorAll() gibi metotlar, belirli bir başlangıç elementinden itibaren CSS seçiciler kullanarak alt elementleri bulmak için çok güçlü araçlardır. Bunlar doğrudan "traversing" kategorisine girmese de, çoğu zaman bir travers işlemine başlamak için veya belirli bir alt element setini toplamak için kullanılırlar. Örneğin, ile sadece içinde yer alan tüm '.child-item' sınıflı elementlere ulaşabiliriz. Bu, global kullanmaktan çok daha performanslı ve bağlamı daraltan bir yaklaşımdır.
Son olarak, travers işlemlerinde performans ve kodun sürdürülebilirliği konularına değinmekte fayda var. Büyük ve karmaşık DOM yapılarında gereksiz yere veya döngü içinde tekrar tekrar travers işlemleri yapmak performansı olumsuz etk
Web uygulamaları geliştirirken, JavaScript ile dinamik olarak içeriği manipüle etmek, kullanıcı etkileşimlerine yanıt vermek veya belirli bir arayüz durumunu güncellemek sıklıkla karşımıza çıkan bir ihtiyaç. HTML belgesini bir ağaç yapısı olarak düşündüğümüzde, bu ağacın dalları ve yaprakları arasında gezintiye çıkmak, yani bir elementten yola çıkarak diğer elementlere ulaşmak, işte tam da bu "DOM Traversing" kavramının temelini oluşturuyor. Birçok geliştirici, özellikle yeni başlayanlar, bu konuda bazen zorlanabiliyor veya en verimli yöntemi bulmakta güçlük çekebiliyor. Belirli bir düğümden yola çıkarak ebeveynine, çocuklarına veya kardeşlerine ulaşmak, göründüğünden çok daha fazla ince detayı barındırıyor...
Bir elementin ebeveynine ulaşmak, genellikle bir olayın tetiklendiği elementi bulduktan sonra, bu elementin içinde bulunduğu daha geniş kapsayıcıya erişmek istediğimizde kritik hale gelir. Örneğin, bir liste öğesine (LI) tıklandığında, tüm listeyi (UL) etkilemek isteyebiliriz. Bu noktada kullanabileceğimiz çeşitli özellikler var. parentNode özelliği, bir düğümün doğrudan ebeveyn düğümünü döndürür. Bu, herhangi bir düğüm türü olabilir; bir element düğümü, metin düğümü veya yorum düğümü... Yani, boşluk karakterleri veya satır sonları bile birer metin düğümü olarak sayılabilir. parentElement ise sadece element düğümlerini döndürür; eğer ebeveyn bir element düğümü değilse (örneğin belgenin kendisi), null döner. Bu ayrım, özellikle tarayıcıların boşlukları nasıl ele aldığına bağlı olarak beklenmedik sonuçlar doğurabileceği için oldukça önemli. Deneyimlerime göre, çoğunlukla bir elementin ebeveyn elementine ihtiyacımız olduğundan, parentElement çoğu senaryoda daha güvenilir bir seçim olacaktır.
CODE
1234567891011121314
Bu bir paragraf.
Bu bir span.
const spanElement = document.querySelector('span');
console.log(spanElement.parentNode); // div#container döner
console.log(spanElement.parentElement); // div#container döner
// Metin düğümünün ebeveynini inceleyelim
const textNode = spanElement.childNodes[0]; // "Bu bir span." metin düğümü
console.log(textNode.parentNode); // span döner
console.log(textNode.parentElement); // span döner
Eğer daha yukarılarda, belirli bir seçiciye uyan ilk ebeveyn elementi arıyorsak, closest() metodu adeta bir kurtarıcı gibidir. Bu metot, kendisine verilen CSS seçiciye uyan en yakın ebeveyn elementini (kendi dahil) bulana kadar DOM ağacında yukarı doğru arama yapar. Bu, özellikle iç içe geçmiş çok sayıda elementle çalışırken ve belirli bir "kapsayıcıya" ulaşmak istediğimizde hayati önem taşır. Örneğin, bir ürün kartının içindeki "Sepete Ekle" butonuna tıkladığımızda, tüm ürün kartının verilerine veya ID'sine ulaşmak isteyebiliriz. closest('.product-card') çağrısı, bu karmaşık yapıda bile hedefe şaşırmadan ulaşmamızı sağlar. Bu metot, performansı da düşünerek tasarlanmıştır; gereksiz yere tüm DOM ağacını taramaz, sadece ihtiyacı olan yere kadar çıkar.
CODE
12345678910111213141516
Ürün Adı
Sepete Ekle
const addToCartButton = document.querySelector('.add-to-cart');
addToCartButton.addEventListener('click', (event) => {
const productCard = event.target.closest('.product-card');
if (productCard) {
const productId = productCard.dataset.productId;
console.log('Ürün ID:', productId); // Ürün ID: 123
}
});
DOM ağacında aşağı doğru inmek, yani bir elementin çocuklarına ulaşmak, belki de en sık karşılaşılan travers etme senaryolarından biridir. Bir listeyi dinamik olarak doldurmak, bir tablonun satırlarına erişmek veya bir formun içindeki alanları kontrol etmek... Tüm bunlar için çocuk elementlere erişim şart. Burada da childNodes ve children arasındaki farkı iyi anlamak gerekiyor. childNodes, bir elementin tüm çocuk düğümlerini içeren bir NodeList döndürür; bu liste element düğümlerini, metin düğümlerini (boşluklar ve satır sonları dahil) ve yorum düğümlerini içerebilir. Dolayısıyla, HTML kodunuzdaki her bir satır sonu veya girinti bile bu liste içinde bir düğüm olarak yerini alabilir. Bu durum, özellikle belirli bir çocuk elemente index ile erişmeye çalıştığınızda beklenmedik sonuçlara yol açabilir.
Öte yandan, children özelliği ise yalnızca element düğümlerini içeren bir HTMLCollection döndürür. Bu, genellikle aradığımız şeydir, çünkü çoğu zaman metin düğümlerini veya yorumları değil, görsel olarak sayfada yer alan diğer elementleri hedef alırız. Bu nedenle, genellikle children kullanmak, kodunuzu daha öngörülebilir ve hataya daha az açık hale getirecektir. Aynı mantık, firstChild ve firstElementChild ile lastChild ve lastElementChild için de geçerlidir.
firstElementChild ve lastElementChild, ilk veya son çocuk elementine ulaşmak için kullanılırken, firstChild ve lastChild herhangi bir türdeki ilk veya son çocuk düğümünü döndürür. Bu ayrımı göz ardı etmek, özellikle dinamik içerik eklerken veya var olan içeriği değiştirirken can sıkıcı hatalara yol açabilir.CODE
123456789101112131415161718
Eleman 1
Eleman 2
Eleman 3
const myList = document.getElementById('myList');
console.log(myList.childNodes.length); // 7 veya 9 olabilir (boşluklara göre değişir)
console.log(myList.children.length); // 3 (sadece li elementleri)
console.log(myList.firstChild); // #text düğümü (satır sonu/boşluk)
console.log(myList.firstElementChild); // Eleman 1
console.log(myList.lastChild); // #text düğümü (satır sonu/boşluk)
console.log(myList.lastElementChild); // Eleman 3
Bir elementin kardeş elementlerine (sibling elements) ulaşmak da benzer bir Node/Element ayrımıyla gelir. nextSibling ve previousSibling özellikleri, sırasıyla bir elementin hemen sonraki veya hemen önceki kardeş düğümünü döndürür. Tıpkı çocuk düğümlerinde olduğu gibi, bunlar da element, metin veya yorum düğümleri olabilir. Yine, boşluklar ve satır sonları burada da karşımıza çıkabilir. Metin düğümlerinin araya girmesi nedeniyle, bir sonraki elemente ulaşmak için bazen birden fazla nextSibling çağrısı yapmamız gerekebilir, ki bu da pek pratik veya güvenilir bir yöntem değildir.
İşte bu noktada nextElementSibling ve previousElementSibling devreye girer. Bu özellikler, bize doğrudan bir sonraki veya bir önceki kardeş elementini döndürür. Yani, aradaki metin düğümlerini veya yorumları tamamen göz ardı ederler. Bu sayede, kodumuz çok daha temiz, okunabilir ve beklenmedik durumlarla karşılaşma olasılığı daha düşük olur. Özellikle dinamik olarak sıralanabilir listeler veya bir formun alanları arasında gezinme gibi senaryolarda bu özellikler paha biçilmezdir.
CODE
123456789101112131415
Öğe A
Öğe B
Öğe C
const itemB = document.querySelector('p');
console.log(itemB.previousSibling); // #text düğümü (satır sonu/boşluk)
console.log(itemB.previousElementSibling); // Öğe A
console.log(itemB.nextSibling); // #text düğümü (satır sonu/boşluk)
console.log(itemB.nextElementSibling); // Öğe C
Peki, tüm bu manuel travers tekniklerinin yanı sıra, daha genel yaklaşımlar yok mu? Elbette var. querySelector() ve querySelectorAll() gibi metotlar, belirli bir başlangıç elementinden itibaren CSS seçiciler kullanarak alt elementleri bulmak için çok güçlü araçlardır. Bunlar doğrudan "traversing" kategorisine girmese de, çoğu zaman bir travers işlemine başlamak için veya belirli bir alt element setini toplamak için kullanılırlar. Örneğin,
CODE
1myElement.querySelectorAll('.child-item')CODE
1myElementCODE
1document.querySelectorAll()Son olarak, travers işlemlerinde performans ve kodun sürdürülebilirliği konularına değinmekte fayda var. Büyük ve karmaşık DOM yapılarında gereksiz yere veya döngü içinde tekrar tekrar travers işlemleri yapmak performansı olumsuz etk