Mert Özen Her Satırda Daha İleri
Frontend JavaScript Temel — #14

JavaScript DOM'a Giriş: Element'leri Seçmek, Değiştirmek ve Oluşturmak

Mert Özen 3 Kas 2026 8 dk 5 görüntülenme
JavaScript DOM'a Giriş: Element'leri Seçmek, Değiştirmek ve Oluşturmak

Tarayıcının HTML'den kurduğu DOM ağacını tanıyor; querySelector ile element seçiyor, textContent ile innerHTML farkını ve XSS riskini görüyor, classList ve createElement ile sayfayı canlı değiştiriyoruz.

Sürüm notu: Bu yazıdaki kodlar ES2026 ve güncel tarayıcılar (Chrome, Edge, Firefox, Safari) içindir.

Önce Tahmin Et

Aşağıdaki dosyayı predict.html adıyla kaydedip tarayıcıda açtığını düşün. Sayfada id'si title olan bir başlık var, script de tam olarak onu arıyor.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Predict</title>
  <script>
    const title = document.getElementById("title");
    console.log(title);
  </script>
</head>
<body>
  <h1 id="title">Hello DOM</h1>
</body>
</html>

Konsolda ne görürsün? Başlık element'inin kendisi mi, yoksa başka bir şey mi? Okumaya devam etmeden önce bir tahmin yap. Cevabı, script'in tam olarak ne zaman çalıştığını konuşurken vereceğiz.

HTML'den Ağaca: DOM Nedir?

Daha önce gördüğümüz JSON yazısında bir metni JSON.parse ile gerçek bir JavaScript object'ine çevirmiştik. Tarayıcı da HTML dosyasıyla benzer bir şey yapar. Dosyayı baştan sona okur, her etiketi bir object'e dönüştürür ve bu object'leri birbirine bağlayarak bir ağaç kurar. Bu ağacın adı DOM, yani Document Object Model.

Bunu bir soy ağacı gibi düşün. En tepede <html> var, onun iki çocuğu <head> ve <body>. Body'nin içindeki başlık, paragraf ve listeler onun çocukları, listelerin içindeki maddeler de torunları. Ağaçtaki her parçaya node denir. Etiketlerden oluşan node'lara element, etiketlerin içindeki metinlere de text node diyoruz.

Burada önemli bir ayrım var: JavaScript senin HTML dosyana dokunmaz. Değiştirdiği şey, tarayıcının bellekte tuttuğu bu ağaçtır. Tarayıcı ağaçtaki her değişikliği anında ekrana yansıtır. Bu yüzden sayfanın kaynağını görüntülediğinde ilk gelen HTML'i, DevTools'un Elements sekmesinde ise ağacın o anki hâlini görürsün. İlk yazıda document.body.style.background ile sayfayı sarıya boyadığımızda yaptığımız şey de buydu.

document: Ağacın Giriş Kapısı

Ağacın tamamına document object'iyle ulaşırsın. Herhangi bir sayfada konsolu açıp şunları tek tek dene:

document.documentElement; // <html> elementi
document.head;            // <head> elementi
document.body;            // <body> elementi
document.title;           // sekmedeki başlık

Küçük bir DevTools hilesi: Elements sekmesinde herhangi bir element'e tıkla, sonra konsola $0 yaz. Seçtiğin element karşına gelir. Bir element'i kodla aramadan önce üzerinde denemeler yapmanın en hızlı yolu budur.

Element Seçmek: getElementById ve querySelector

Bir element'i değiştirmek için önce onu bulman gerekir. Aşağıdaki dosyayı kaydedip aç ve konsola bak:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Selectors</title>
</head>
<body>
  <h1 id="title">Shopping List</h1>
  <ul class="list">
    <li class="item">Bread</li>
    <li class="item on-sale">Milk</li>
    <li class="item">Cheese</li>
  </ul>
  <script>
    const title = document.getElementById("title");
    const firstItem = document.querySelector(".item");
    const onSale = document.querySelector("li.on-sale");
    const allItems = document.querySelectorAll(".list .item");

    console.log(title.textContent);     // "Shopping List"
    console.log(firstItem.textContent); // "Bread"
    console.log(onSale.textContent);    // "Milk"
    console.log(allItems.length);       // 3

    allItems.forEach((item) => console.log(item.textContent));
  </script>
</body>
</html>

getElementById en eski ve en doğrudan method: id'yi verirsin, o id'ye sahip element'i alırsın. Bulamazsa null döner. querySelector ise CSS'te kullandığın selector'ların aynısını kabul eder: #id, .class, li.on-sale, .list .item. Eşleşen ilk element'i döndürür, eşleşme yoksa yine null.

querySelectorAll eşleşen bütün element'leri bir NodeList içinde verir. NodeList bir array değildir ama ona çok benzer: length'i vardır, index ile okunur, forEach ve for...of ile gezilir. map ya da filter gibi method'ları yoktur. Onlara ihtiyacın olursa daha önce gördüğümüz spread ile gerçek bir array'e çevirebilirsin: [...allItems].

Bu method'ları yalnızca document üzerinde değil, herhangi bir element üzerinde de çağırabilirsin. list.querySelector(".item") aramayı sadece o listenin içinde yapar. Bir de script'i neden body'nin en sonuna koyduğumuza dikkat et; birazdan bunun önemli bir sebebi olduğunu göreceğiz.

İçeriği Değiştirmek: textContent ve innerHTML

Element'i bulduktan sonra içeriğini değiştirmenin iki yaygın yolu var. Alışveriş listesi sayfası açıkken konsola şunu yapıştırıp dene:

const h1 = document.querySelector("#title");

h1.textContent = "<em>Urgent</em> List";
// Ekranda aynen yazar: <em>Urgent</em> List

h1.innerHTML = "<em>Urgent</em> List";
// "Urgent" eğik yazılır, etiket HTML olarak yorumlandı

textContent verdiğin değeri düz metin olarak yerleştirir. İçinde etiket gibi görünen bir şey olsa bile tarayıcı onu yorumlamaz, harfi harfine ekrana basar. innerHTML ise değeri HTML olarak ayrıştırır ve içinden yeni element'ler üretir. Okurken de fark aynıdır: textContent sadece metni, innerHTML etiketlerle birlikte içeriği verir.

Bu fark masum görünse de güvenlik açısından çok önemli. Kullanıcının yazdığı bir yorumu sayfaya innerHTML ile bastığını düşün. Aynı sayfanın konsolunda dene:

const box = document.querySelector("#title");
const comment = '<img src="x" onerror="alert(1)">';

box.innerHTML = comment;   // Resim yüklenemez, onerror çalışır: alert açılır
box.textContent = comment; // Sadece metin olarak görünür, hiçbir şey çalışmaz

Buna XSS (Cross-Site Scripting) denir: saldırgan, senin sayfanda kendi kodunu çalıştırır. innerHTML ile eklenen bir <script> etiketi çalışmaz ama bu seni korumaz; yukarıdaki gibi bir onerror attribute'u yeterlidir. Kuralı basit tut: kullanıcıdan, adres çubuğundan ya da bir sunucudan gelen her şeyi textContent ile yaz. innerHTML'i yalnızca kendi yazdığın, içinde dışarıdan veri olmayan HTML için kullan.

Bir de innerText göreceksin. O da metin verir ama sayfanın görünümünü hesaba katar; örneğin CSS ile gizlenmiş metni döndürmez. Günlük işlerin çoğunda textContent daha hızlı ve daha öngörülebilir bir tercih.

Attribute, classList ve style

Element'lerin metninin yanında attribute'ları ve görünümü de değiştirilebilir. Bu dosyayı kaydedip aç:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Attributes and classList</title>
  <style>
    .box { padding: 16px; border: 2px solid gray; }
    .active { border-color: green; background: honeydew; }
  </style>
</head>
<body>
  <div id="card" class="box">Card</div>
  <a id="link" href="/about">About us</a>
  <script>
    const card = document.getElementById("card");
    const link = document.getElementById("link");

    console.log(link.getAttribute("href")); // "/about"
    console.log(link.href);                 // tam adres
    link.setAttribute("title", "Who are we?");

    card.classList.add("active");
    console.log(card.classList.contains("active")); // true
    card.classList.toggle("active"); // varsa kaldırır, yoksa ekler
    card.classList.toggle("active");

    card.style.color = "darkred";
    card.style.fontSize = "20px";
  </script>
</body>
</html>

getAttribute ve setAttribute HTML'de yazanla doğrudan çalışır. Birçok attribute'un aynı adlı bir property karşılığı da vardır; link.href gibi. Aradaki ince fark çıktıda görünüyor: attribute senin yazdığın /about değerini, property ise tarayıcının çözdüğü tam adresi verir.

Class'lar için classList kullan. add, remove, toggle ve contains diğer class'lara dokunmadan tek bir class üzerinde çalışır. className'e yeni bir değer atamak ise element'in bütün class'larını tek seferde değiştirir; çoğu zaman istediğin bu değildir.

style ile element'in satır içi stilini değiştirirsin. CSS'teki tireli isimler burada camelCase olur: font-size yerine fontSize, background-color yerine backgroundColor. İyi bir alışkanlık şu: tasarımı CSS'te class olarak tanımla, JavaScript'le sadece class ekleyip çıkar. style'ı, bir ilerleme çubuğunun genişliği gibi gerçekten hesaplanan değerler için sakla.

Element Oluşturmak, Eklemek ve Silmek

DOM'u sadece değiştirmekle kalmaz, ona yeni dallar da eklersin:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Add and Remove</title>
</head>
<body>
  <ul id="list">
    <li>Bread</li>
    <li id="milk">Milk</li>
  </ul>
  <script>
    const list = document.getElementById("list");

    const newItem = document.createElement("li");
    newItem.textContent = "Olives";
    list.append(newItem);   // sona ekler

    const first = document.createElement("li");
    first.textContent = "Tomatoes";
    list.prepend(first);    // başa ekler

    document.getElementById("milk").remove();

    const fruits = ["Apple", "Pear", "Cherry"];
    for (const fruit of fruits) {
      const li = document.createElement("li");
      li.textContent = fruit;
      list.append(li);
    }
  </script>
</body>
</html>

createElement bellekte yeni bir element üretir ama onu sayfaya koymaz. Element, sen onu ağaçta bir yere bağlayana kadar görünmez. append element'i sona, prepend başa ekler. İkisi de birden fazla değer ve düz metin kabul eder: list.append(a, b, "text"). Eski kodlarda appendChild göreceksin; aynı işi yapar ama tek bir node alır. remove ise element'i bulunduğu yerden söküp atar.

Son kısımdaki döngü bu yazının belki de en kullanışlı kalıbı: veriyi bir array'de tut, döngüyle dolaş, her kayıt için bir element oluştur ve sayfaya ekle. Bir ürün listesi, bir yorum akışı, bir arama sonucu sayfası temelde bu kalıbın daha büyük hâlleridir. Çok sayıda element'i daha verimli eklemenin yollarını ilerleyen yazılarda detaylandıracağız.

Sık Düşülen Tuzaklar

Script, DOM'dan önce çalışırsa

Baştaki sorunun cevabı: konsolda null görürsün. Tarayıcı HTML'i yukarıdan aşağıya okur ve bir <script> etiketine geldiğinde onu hemen çalıştırır. Script <head> içindeyken tarayıcı henüz body'ye gelmemiştir, yani title adında bir element ağaçta henüz yoktur. Bir adım ileri gidip title.textContent okumaya çalışsaydın şu hatayı alırdın: TypeError: Cannot read properties of null (reading 'textContent').

Çözüm için iki yol var. Ya script'i body'nin en sonuna koyarsın, ya da daha önce gördüğümüz defer ile harici dosyayı head'de bırakırsın:

<head>
  <meta charset="UTF-8">
  <script src="app.js" defer></script>
</head>

defer, tarayıcıya script'i indirmesini ama sayfanın tamamı okunana kadar çalıştırmamasını söyler. Bu attribute sadece src ile bağlanan harici dosyalarda işe yarar; satır içi bir script'e eklersen hiçbir etkisi olmaz.

NodeList'e element gibi davranmak

document.querySelectorAll(".item").textContent = "None" yazmak hata vermez ama hiçbir şeyi değiştirmez. NodeList bir kutudur, içindeki element'ler değil. Değişikliği her bir element için döngüyle yapman gerekir.

Selector yazımını karıştırmak

getElementById düz id ister: getElementById("title"). querySelector ise CSS selector'ı ister: querySelector("#title"). Birinin kuralını diğerinde kullanırsan sessizce null alırsın.

Döngüde innerHTML +=

list.innerHTML += "<li>New</li>" masum görünür ama tarayıcı her seferinde listenin bütün içeriğini metne çevirip baştan oluşturur. Hem yavaştır hem de eski element'lere bağladığın her şey kaybolur. Bir sonraki yazıda göreceğimiz event listener'lar da buna dahil. createElement ve append bu sorunu yaşamaz.

Küçük Bir Deneme

★☆☆ Alışveriş listesi dosyasında querySelectorAll ile bütün ürünleri seç ve her birinin metnini daha önce gördüğümüz toUpperCase ile büyük harfe çevir.

★★☆ Çalıştırmadan önce tahmin et, sonra herhangi bir sayfanın konsolunda dene:

const p = document.createElement("p");
p.textContent = "<b>Bold?</b>";
console.log(p.innerHTML);
console.log(document.body.contains(p));

★★★ { name: "Pen", price: 40 } biçiminde object'lerden oluşan bir array hazırla. Her ürün için bir <li> oluştur, metnini textContent ile "Pen: $40" şeklinde yaz ve fiyatı 100'den büyük olanlara classList ile expensive class'ını ekle. CSS'te bu class'a kırmızı bir renk ver.

Şu ana kadar sayfayı hep biz değiştirdik, kullanıcı sadece seyretti. Bir sonraki yazıda işin içine kullanıcıyı katacağız: bir butona tıklandığında, bir kutuya yazı yazıldığında ya da bir tuşa basıldığında çalışan kodu event'lerle yazacağız.