← Kursa Dön
📄 Text · 30 min

DOM Nedir?

Giriş — Web Sayfasının Perde Arkası

Bir web sayfasını ziyaret ettiğinde, tarayıcı sadece bir HTML dosyası indirmez — o dosyayı okur, anlar, hafızasında canlı bir model oluşturur ve ekrana çizer. İşte bu canlı model, DOM (Document Object Model) olarak adlandırılır. JavaScript'in sayfadaki bir butona tıklandığında renk değiştirmesi, bir form gönderildiğinde veriyi kontrol etmesi, kaydırma yapıldığında yeni içerik yüklemesi — tüm bunlar DOM sayesinde mümkündür.

Şimdiye kadar JavaScript ile değişkenler, fonksiyonlar, diziler ve nesnelerle çalıştık. Ama bunların hiçbiri ekranda bir şey göstermiyordu — her şey konsolda kalıyordu. Bu bölümle birlikte JavaScript'i kullanıcının gördüğü dünyayla buluşturuyoruz. Ve bu buluşmanın köprüsü DOM.

Analoji: Bir tiyatro sahnesini düşün. HTML dosyası senaryodur — oyunun metnini içerir. CSS kostüm ve dekordur — sahnenin görünümünü belirler. JavaScript ise yönetmen ve aktörlerdir — sahnede ne olacağını, ne zaman olacağını kontrol eder. Ama yönetmenin sahneyi kontrol edebilmesi için, sahnenin bir maketi (modeli) olmalı — neyin nerede olduğunu, hangi aktörün hangi kostümü giydiğini bilmeli. İşte DOM, bu maketin ta kendisi: sahnenin JavaScript'in anlayacağı dilden yapılmış canlı haritası.


Document Object Model — Tanım

DOM, tarayıcının HTML belgesini temsil ettiği programlama arayüzüdür (API). HTML'deki her bir etiket, her metin parçası, hatta yorumlar bile DOM'da bir nesne (node) olarak temsil edilir. JavaScript bu nesnelere erişir, okur, değiştirir, siler veya yenilerini ekler.

Önemli noktalar:

  1. DOM, HTML değildir. HTML, tarayıcıya gönderilen metin dosyasıdır. DOM ise tarayıcının o metni işleyerek hafızasında oluşturduğu nesne ağacıdır.

  2. DOM canlıdır. JavaScript ile DOM'u değiştirdiğinizde, ekrandaki görüntü anında güncellenir (rendering pipeline tetiklenir).

  3. DOM bir standarttır. W3C ve WHATWG tarafından belirlenen bir spesifikasyondur — tüm tarayıcılar bu standarda uyar.

<!-- Bu bir HTML belgesi — düz metin -->
<!DOCTYPE html>
<html lang="tr">
<head>
  <title>Merhaba</title>
</head>
<body>
  <h1>Başlık</h1>
  <p>Paragraf metni</p>
</body>
</html>

Tarayıcı bu HTML'i parse ettiğinde, hafızasında şöyle bir ağaç yapısı oluşturur:

document
└── html (lang="tr")
    ├── head
    │   └── title
    │       └── "Merhaba" (text)
    └── body
        ├── h1
        │   └── "Başlık" (text)
        └── p
            └── "Paragraf metni" (text)

Bu ağaçtaki her kutu bir node (düğüm), kutuların arasındaki çizgiler ise parent-child (ebeveyn-çocuk) ilişkisidir.


DOM Ağacı (DOM Tree)

DOM ağacı, HTML belgesinin hiyerarşik bir temsilidir. Tıpkı bir soy ağacı gibi, her node'un bir ebeveyni (parent), çocukları (children) ve kardeşleri (siblings) olabilir.

Terminoloji

        document
           │
         <html>          ← root element
        /      \
    <head>    <body>      ← siblings (kardeşler)
      │       /    \
   <title>  <h1>   <p>   ← children of body
      │       │      │
   "Merhaba" "Başlık" "Paragraf"  ← text nodes
  • Root: Ağacın en üst noktası — document nesnesi

  • Parent (Ebeveyn): Bir node'un hemen üstündeki node. <body>, <h1>'in parent'ıdır

  • Child (Çocuk): Bir node'un hemen altındaki node. <h1>, <body>'nin child'ıdır

  • Sibling (Kardeş): Aynı parent'ı paylaşan node'lar. <h1> ve <p>, kardeştir

  • Descendant (Torun): Bir node'un altındaki tüm node'lar (doğrudan veya dolaylı)

  • Ancestor (Ata): Bir node'un üstündeki tüm node'lar

// DOM ağacında gezinme
const body = document.body;

// Parent'a erişim
console.log(body.parentNode);       // <html> elementi
console.log(body.parentElement);    // <html> elementi

// Çocuklara erişim
console.log(body.childNodes);       // NodeList — TÜM node'lar (text dahil)
console.log(body.children);         // HTMLCollection — sadece ELEMENT node'lar

// Kardeşlere erişim
const h1 = document.querySelector("h1");
console.log(h1.nextElementSibling);     // <p> elementi
console.log(h1.previousElementSibling); // null (ilk çocuk)

// İlk ve son çocuk
console.log(body.firstElementChild);  // <h1>
console.log(body.lastElementChild);   // <p>

⚠️ Dikkat: childNodes ile children arasında kritik bir fark var. childNodes, tüm node tiplerini döndürür — text node'lar, yorumlar dahil. children ise sadece element node'ları döndürür. HTML'de etiketler arasındaki boşluk ve satır sonları bile text node olarak sayılır:

// HTML:
// <body>
//   <h1>Başlık</h1>
//   <p>Paragraf</p>
// </body>

console.log(document.body.childNodes.length);  // 5! (3 text node + 2 element)
// childNodes: [text("\n  "), <h1>, text("\n  "), <p>, text("\n")]

console.log(document.body.children.length);    // 2 (sadece h1 ve p)
// children: [<h1>, <p>]

💡 İpucu: Elemanlara erişmek istiyorsanız neredeyse her zaman children, firstElementChild, nextElementSibling gibi "Element" versiyonlarını kullanın. childNodes, firstChild, nextSibling gibi olanlar text ve yorum node'larını da içerir — genellikle bunları istemezsiniz.


Node Türleri

DOM ağacındaki her şey bir Node'dur. Ama her node aynı tipte değildir. Tıpkı bir şehirde binaların, parkların, yolların, tabelaların farklı şeyler olması gibi — hepsi şehrin parçasıdır ama farklı amaçlara hizmet eder.

Ana Node Tipleri

SabitDeğerAçıklamaÖrnek
Node.ELEMENT_NODE1HTML elementi<div>, <p>, <span>
Node.TEXT_NODE3Metin içeriği"Merhaba Dünya"
Node.COMMENT_NODE8HTML yorumu<!-- yorum -->
Node.DOCUMENT_NODE9Document nesnesidocument
Node.DOCUMENT_TYPE_NODE10DOCTYPE<!DOCTYPE html>
Node.DOCUMENT_FRAGMENT_NODE11Belge parçasıDocumentFragment
// Node tipini kontrol etme
const h1 = document.querySelector("h1");

console.log(h1.nodeType);    // 1 (ELEMENT_NODE)
console.log(h1.nodeName);    // "H1"
console.log(h1.nodeValue);   // null (element node'larında değer yoktur)

// Text node
const textNode = h1.firstChild; // h1'in içindeki metin
console.log(textNode.nodeType);   // 3 (TEXT_NODE)
console.log(textNode.nodeName);   // "#text"
console.log(textNode.nodeValue);  // "Başlık" (metnin kendisi)

// Yorum node
// HTML: <!-- Bu bir yorum -->
const yorum = document.body.childNodes[0]; // İlk child bir yorumsa
if (yorum.nodeType === Node.COMMENT_NODE) {
  console.log(yorum.nodeValue); // " Bu bir yorum "
}

Element vs Node

Bu iki kavram sık karıştırılır. İlişkileri şöyledir:

                  Node
                   │
    ┌──────────────┼───────────────┐
    │              │               │
 Element       Text Node     Comment Node
    │
 HTMLElement
    │
 ┌──┴──┬──────┬───────┐
 │     │      │       │
HTMLDiv HTMLParagraph  HTMLInput  ...
  • Node, DOM'daki her şeyin temel sınıfıdır — elementler, metinler, yorumlar

  • Element, sadece HTML etiketlerini temsil eder — <div>, <p>, <span>

  • HTMLElement, Element'in alt sınıfıdır — HTML'e özgü özellikler ekler

const div = document.querySelector("div");

// Her element bir node'dur, ama her node bir element değildir
console.log(div instanceof Node);        // true
console.log(div instanceof Element);     // true
console.log(div instanceof HTMLElement); // true

const textNode = div.firstChild;
console.log(textNode instanceof Node);    // true
console.log(textNode instanceof Element); // false — text, element değil!

document Nesnesi

document, DOM ağacının kök nesnesidir. Tarayıcı bir HTML sayfası yüklediğinde, otomatik olarak global scope'a bir document nesnesi koyar. Tüm DOM işlemleri bu nesne üzerinden yapılır.

// document hakkında temel bilgiler
console.log(document.nodeType);     // 9 (DOCUMENT_NODE)
console.log(document.nodeName);     // "#document"

// Belge bilgileri
console.log(document.title);        // Sayfanın başlığı
console.log(document.URL);          // Sayfanın URL'si
console.log(document.domain);       // Domain adı
console.log(document.lastModified); // Son değişiklik tarihi
console.log(document.readyState);   // "loading" | "interactive" | "complete"

// Kök elementlere erişim
console.log(document.documentElement); // <html> elementi
console.log(document.head);           // <head> elementi
console.log(document.body);           // <body> elementi

// Tüm formlar, linkler, görseller
console.log(document.forms);   // HTMLCollection — sayfadaki tüm formlar
console.log(document.links);   // HTMLCollection — href'i olan tüm <a> ve <area>
console.log(document.images);  // HTMLCollection — tüm <img> elementleri

window vs document

Bu ikisi farklı şeylerdir:

// window → tarayıcı penceresini temsil eder (global nesne)
// Özellikler: innerWidth, innerHeight, location, history, localStorage...
// Metotlar: alert(), setTimeout(), fetch()...

// document → yüklü HTML belgesini temsil eder (DOM ağacı)
// Özellikler: title, body, head, forms...
// Metotlar: querySelector(), createElement(), getElementById()...

console.log(window.document === document); // true
// document aslında window'un bir özelliği

// window global scope'tur — doğrudan erişilir
console.log(innerWidth);   // window.innerWidth ile aynı
console.log(location.href); // window.location.href ile aynı

Tarayıcı Rendering Pipeline

Bir web sayfasının HTML dosyasından ekrandaki piksellere dönüşme süreci, rendering pipeline (işleme hattı) olarak adlandırılır. Bu süreci anlamak, performanslı JavaScript yazmak için kritiktir — çünkü JavaScript ile DOM'u her değiştirdiğinizde, bu hattın bir kısmı veya tamamı yeniden çalışır.

Adım Adım Rendering Süreci

  HTML dosyası          CSS dosyası
      │                     │
      ▼                     ▼
  [HTML Parser]        [CSS Parser]
      │                     │
      ▼                     ▼
   DOM Tree            CSSOM Tree
      │                     │
      └──────┬──────────────┘
             ▼
        Render Tree
             │
             ▼
     Layout (Reflow)    ← "Her element nerede, ne boyutta?"
             │
             ▼
     Paint (Repaint)    ← "Her pikselin rengi ne?"
             │
             ▼
      Composite         ← "Katmanları birleştir, ekrana çiz"
             │
             ▼
        🖥️ Ekran

Her Adımın Detayı

1. HTML Parsing → DOM Tree

Tarayıcı, HTML dosyasını yukarıdan aşağıya okur (parse). Her etiketi bir node'a dönüştürerek DOM ağacını oluşturur. <script> etiketiyle karşılaşırsa, JavaScript'i çalıştırmak için parse işlemini durdurur (bu yüzden script etiketleri body'nin sonuna konur veya defer/async kullanılır).

<!-- ❌ Kötü — HTML parse'ını bloklar -->
<head>
  <script src="uygulamam.js"></script>
</head>

<!-- ✅ İyi — parse tamamlandıktan sonra çalışır -->
<body>
  <!-- ... içerik ... -->
  <script src="uygulamam.js"></script>
</body>

<!-- ✅ En iyi — parse'ı bloklamaz, DOM hazır olunca çalışır -->
<head>
  <script src="uygulamam.js" defer></script>
</head>

2. CSS Parsing → CSSOM Tree

CSS dosyaları parse edilerek CSSOM (CSS Object Model) ağacı oluşturulur. CSSOM, hangi elementin hangi stil kurallarını aldığını belirler. CSS, render-blocking'dir — CSSOM oluşturulmadan render tree oluşturulamaz.

3. Render Tree

DOM ve CSSOM birleştirilerek render tree oluşturulur. Render tree, ekranda görünür olan elementleri içerir. display: none olan elementler render tree'de yer almaz (ama visibility: hidden olan elementler yer alır — sadece görünmezdir, yer kaplar).

4. Layout (Reflow)

Her elementin ekrandaki pozisyonu ve boyutu hesaplanır. Kutunun genişliği, yüksekliği, margin, padding, border — hepsi bu adımda belirlenir. Bu hesaplama, viewport boyutuna ve diğer elementlere bağlıdır.

5. Paint (Repaint)

Her pikselin rengi belirlenir. Arkaplan renkleri, metin, kenarlıklar, gölgeler, görseller — görsel detayların tamamı bu adımda piksellere dönüşür.

6. Composite

Modern tarayıcılar, sayfayı katmanlara (layers) ayırır. Her katman ayrı ayrı çizilir ve sonra birleştirilir (compositing). transform, opacity gibi özellikler ayrı katmanlarda işlenir — bu yüzden çok performanslıdır.

JavaScript'in Rendering'e Etkisi

JavaScript ile DOM'u değiştirdiğinizde, rendering pipeline'ın hangi adımından tetiklendiği büyük fark yaratır:

// 🔴 Layout tetikleyen özellikler (EN PAHALI)
// width, height, top, left, margin, padding, border, font-size...
element.style.width = "200px";  // Layout → Paint → Composite

// 🟡 Paint tetikleyen özellikler (ORTA)
// color, background-color, box-shadow, border-color...
element.style.backgroundColor = "red";  // Paint → Composite (Layout atlanır)

// 🟢 Sadece Composite tetikleyenler (EN UCUZ)
// transform, opacity
element.style.transform = "translateX(100px)";  // Sadece Composite!
element.style.opacity = "0.5";                   // Sadece Composite!

⚠️ Dikkat: Bu yüzden animasyonlarda top/left yerine transform: translate(), width/height yerine transform: scale() kullanmak çok daha performanslıdır. Layout tetiklemekten kaçının!


Reflow ve Repaint

Rendering pipeline'ın en kritik kavramlarından ikisi reflow ve repaint'tir. Bunları tetikleyen işlemleri bilmek, performanslı DOM manipülasyonu için şarttır.

Reflow (Layout Yeniden Hesaplama)

Reflow, elementlerin pozisyon ve boyutlarının yeniden hesaplanmasıdır. En pahalı DOM işlemidir çünkü bir elementin boyutu değiştiğinde, etrafındaki elementler de etkilenebilir — domino etkisi yaratır.

// Reflow tetikleyen işlemler:
// 1. Element ekleme/silme
document.body.appendChild(yeniDiv);

// 2. Boyut/pozisyon değiştiren stiller
element.style.width = "300px";
element.style.padding = "20px";
element.style.fontSize = "18px";

// 3. Pencere boyutu değişikliği
// window resize olayı

// 4. Layout bilgisi okuma (GIZLI TUZAK!)
// Aşağıdaki özellikler OKUNDUĞUNDA bile reflow tetikleyebilir:
const genislik = element.offsetWidth;
const yukseklik = element.offsetHeight;
const rect = element.getBoundingClientRect();
const scrollTop = element.scrollTop;

Layout Thrashing — Performans Katili

Layout thrashing, reflow'u gereksiz yere tekrar tekrar tetiklemendir. Okuma ve yazma işlemlerini birbirine karıştırdığında ortaya çıkar:

// ❌ Layout Thrashing — her döngüde okuma + yazma
const kutular = document.querySelectorAll(".kutu");

for (let i = 0; i < kutular.length; i++) {
  const genislik = kutular[i].offsetWidth; // OKUMA → reflow tetikler
  kutular[i].style.width = genislik + 10 + "px"; // YAZMA → reflow tetikler
  // Her döngüde 2 reflow! 100 kutuda 200 reflow!
}

// ✅ Batch okuma + Batch yazma
const genislikler = [];

// Önce tüm okumaları yap
for (let i = 0; i < kutular.length; i++) {
  genislikler.push(kutular[i].offsetWidth);
}

// Sonra tüm yazmaları yap
for (let i = 0; i < kutular.length; i++) {
  kutular[i].style.width = genislikler[i] + 10 + "px";
}
// Tek reflow!

💡 İpucu: Modern araçlarla layout thrashing'i tespit edebilirsiniz: Chrome DevTools → Performance sekmesi → "Layout" olaylarını inceleyin. Çok fazla "Forced reflow" uyarısı görüyorsanız, okuma ve yazma işlemlerini ayırmanız gerekir.

Repaint (Yeniden Boyama)

Repaint, layout değişmeden sadece görsel özelliklerin yeniden çizilmesidir. Reflow'dan daha ucuzdur ama yine de maliyetlidir.

// Sadece repaint tetikleyen özellikler (layout DEĞİŞMEZ):
element.style.color = "blue";
element.style.backgroundColor = "#f5f5f5";
element.style.visibility = "hidden"; // Yer kaplamaya devam eder!
element.style.boxShadow = "0 2px 4px rgba(0,0,0,0.1)";
element.style.outline = "2px solid red";

DOM Manipülasyonunda Performans

DOM işlemleri doğası gereği pahalıdır. Her değişiklik, rendering pipeline'ın bir kısmını yeniden çalıştırır. Ama doğru tekniklerle bu maliyeti minimize edebilirsiniz.

DocumentFragment — Toplu Ekleme

Birden fazla element eklerken, her birini tek tek DOM'a eklemek yerine, bir DocumentFragment içinde toplu ekleyin:

// ❌ Her ekleme ayrı reflow tetikler
const liste = document.querySelector("#urunListesi");

for (let i = 0; i < 1000; i++) {
  const li = document.createElement("li");
  li.textContent = `Ürün ${i + 1}`;
  liste.appendChild(li); // 1000 kez DOM güncellenir!
}

// ✅ DocumentFragment ile toplu ekleme — TEK reflow
const fragment = document.createDocumentFragment();

for (let i = 0; i < 1000; i++) {
  const li = document.createElement("li");
  li.textContent = `Ürün ${i + 1}`;
  fragment.appendChild(li); // Hafızada, DOM'a dokunmaz
}

liste.appendChild(fragment); // Tek seferde DOM'a eklenir — tek reflow!

DocumentFragment, bellekte yaşayan hafif bir "sahte belge"dir. DOM ağacının parçası değildir, bu yüzden içine element eklemek reflow tetiklemez. Fragment DOM'a eklendiğinde, kendisi eklenmez — sadece içindeki çocuklar eklenir.

innerHTML ile Toplu Güncelleme

Basit listeler için innerHTML de etkili bir yöntemdir:

// ✅ innerHTML ile toplu ekleme — tek reflow
const liste = document.querySelector("#urunListesi");
let html = "";

for (let i = 0; i < 1000; i++) {
  html += `<li>Ürün ${i + 1}</li>`;
}

liste.innerHTML = html; // Tek seferde parse ve ekleme

⚠️ Dikkat: innerHTML mevcut event listener'ları kaldırır! Eğer listedeki elementlere bağlı event'ler varsa, bu yöntem onları yok eder. Bu durumda DocumentFragment veya event delegation tercih edin.

requestAnimationFrame — Görsel Güncellemeler

Stil değişikliklerini tarayıcının bir sonraki render döngüsüyle senkronize etmek için requestAnimationFrame kullanın:

// ❌ Stil güncellemesi hemen yapılır — tarayıcı optimize edemeyebilir
element.style.transform = "translateX(100px)";

// ✅ Tarayıcının bir sonraki kare çiziminde güncelle
requestAnimationFrame(() => {
  element.style.transform = "translateX(100px)";
});

// Animasyon döngüsü
function animasyonDongusu(zaman) {
  // Bir sonraki kareye kadar hesaplamalar yap
  const x = Math.sin(zaman / 1000) * 100;
  element.style.transform = `translateX(${x}px)`;

  requestAnimationFrame(animasyonDongusu);
}

requestAnimationFrame(animasyonDongusu);

DOMContentLoaded vs load

JavaScript ile DOM'a erişmek istiyorsanız, DOM'un hazır olmasını beklemeniz gerekir. İki önemli olay bunu kontrol eder:

// DOMContentLoaded — DOM ağacı hazır (resimler, CSS yüklenmemiş olabilir)
document.addEventListener("DOMContentLoaded", () => {
  console.log("DOM hazır! Elementlere erişebilirim.");
  const baslik = document.querySelector("h1");
  console.log(baslik.textContent);
});

// load — HER ŞEY yüklendi (resimler, CSS, iframe'ler dahil)
window.addEventListener("load", () => {
  console.log("Sayfa tamamen yüklendi!");
  // Resim boyutlarına erişim güvenli
  const resim = document.querySelector("img");
  console.log(resim.naturalWidth, resim.naturalHeight);
});

// readyState ile kontrol
console.log(document.readyState);
// "loading"     → HTML parse ediliyor
// "interactive" → DOM hazır (DOMContentLoaded ile aynı an)
// "complete"    → Her şey yüklendi (load ile aynı an)

Script Yükleme Stratejileri

<!-- 1. Normal (defer/async yok) — parse'ı BLOKLAR -->
<script src="app.js"></script>
<!-- Parse durur → script indirilir → çalıştırılır → parse devam eder -->

<!-- 2. async — İndirilirken parse devam eder, hazır olunca çalışır -->
<script async src="analytics.js"></script>
<!-- Parse + indirme paralel → indirilince parse durur → çalışır → parse devam -->
<!-- Sıra GARANTİ DEĞİL! Bağımsız scriptler için (analytics, reklam) -->

<!-- 3. defer — İndirilirken parse devam eder, parse bittikten sonra çalışır -->
<script defer src="app.js"></script>
<!-- Parse + indirme paralel → parse biter → script çalışır -->
<!-- Sıra GARANTİ! DOM'a bağımlı scriptler için (ÖNERİLEN) -->
Normal:   HTML ████████──SCRIPT──████████
async:    HTML ████████████████████
                    ──SCRIPT──↑(çalışır)
defer:    HTML ████████████████████
                    ──SCRIPT──       ↑(parse bitti, çalışır)

💡 İpucu: Çoğu durumda defer en iyi seçenektir. DOM'a erişim gerektiğinde (ki genellikle gerekir) güvenlidir, yükleme performansını olumsuz etkilemez ve scriptler sıralı çalışır.


DevTools ile DOM'u Keşfetme

Chrome DevTools, DOM'u inceleme ve debug etme için güçlü araçlar sunar:

Elements Paneli

1. Sayfaya sağ tıklayın → "İncele" (Inspect)
2. Elements sekmesine gidin
3. DOM ağacını görsel olarak inceleyin:
   - Elementleri tıklayarak seçin
   - Sağ tarafta Styles, Computed, Layout bilgilerini görün
   - Çift tıklayarak HTML'i düzenleyin
   - Element üzerine geldiğinizde sayfada vurgulanır

Console'da DOM Sorgulama

// DevTools Console'da hızlı DOM keşfi

// $0 — Elements panelinde seçili element
console.log($0);              // Seçili element
console.log($0.classList);    // Class'ları
console.log($0.children);    // Çocukları

// $() — querySelector kısayolu (sadece DevTools'ta)
$("h1");                      // İlk h1
$$(".card");                  // Tüm .card elementleri (querySelectorAll)

// getEventListeners() — bir elementin event'leri (sadece DevTools)
getEventListeners($0);        // Seçili elementin tüm event listener'ları

// DOM değişikliklerini izleme
// Elements panelinde element → sağ tık → "Break on" →
//   "subtree modifications" — çocuklar değiştiğinde
//   "attribute modifications" — attribute değiştiğinde
//   "node removal" — element silindiğinde

Yaygın Hatalar ve Tuzaklar

Hata 1: DOM Hazır Olmadan Erişmeye Çalışmak

<!-- ❌ script, body'den önce — elementler henüz yok -->
<head>
  <script>
    const btn = document.querySelector("#myBtn");
    console.log(btn); // null! Element henüz oluşturulmadı
  </script>
</head>
<body>
  <button id="myBtn">Tıkla</button>
</body>

<!-- ✅ Çözüm 1: Script'i body sonuna koy -->
<body>
  <button id="myBtn">Tıkla</button>
  <script>
    const btn = document.querySelector("#myBtn");
    console.log(btn); // ✅ <button id="myBtn">
  </script>
</body>

<!-- ✅ Çözüm 2: defer kullan -->
<head>
  <script defer src="app.js"></script>
</head>

<!-- ✅ Çözüm 3: DOMContentLoaded dinle -->
<head>
  <script>
    document.addEventListener("DOMContentLoaded", () => {
      const btn = document.querySelector("#myBtn");
      console.log(btn); // ✅ <button id="myBtn">
    });
  </script>
</head>

Hata 2: childNodes ile children'ı Karıştırmak

// Boşluk ve satır sonları text node olarak sayılır!
// <ul>
//   <li>A</li>
//   <li>B</li>
// </ul>

const ul = document.querySelector("ul");
console.log(ul.childNodes.length);  // 5 (3 text + 2 li)
console.log(ul.children.length);    // 2 (sadece li'ler)

// ✅ Element çocukları için her zaman .children kullanın

Hata 3: Live vs Static Koleksiyonlar

// getElementsBy* → LIVE koleksiyon (DOM değişince güncellenir)
const canlıListe = document.getElementsByClassName("item");

// querySelectorAll → STATIC koleksiyon (anlık snapshot)
const statikListe = document.querySelectorAll(".item");

// Farkı görelim:
const yeniDiv = document.createElement("div");
yeniDiv.className = "item";
document.body.appendChild(yeniDiv);

console.log(canlıListe.length);  // Güncellendi! (eski + 1)
console.log(statikListe.length); // Değişmedi! (eski sayı)

// ⚠️ Live koleksiyonla döngü yaparken eleman silmek sorun çıkarır:
// for (let i = 0; i < canliListe.length; i++) {
//   canliListe[i].remove(); // Silince length azalır, index kayar!
// }
// ✅ Statik koleksiyon (querySelectorAll) ile güvenle silebilirsiniz

Gerçek Dünya Örneği: DOM Ağacı Görselleştirici

Bir elementin DOM ağacını konsola ağaç formatında yazdıran yardımcı fonksiyon:

function domAgaciYazdir(element, girinti = 0) {
  const bosluk = "  ".repeat(girinti);
  const nodeAdi = element.tagName
    ? element.tagName.toLowerCase()
    : "#text";

  // Element node
  if (element.nodeType === Node.ELEMENT_NODE) {
    // Attribute'ları topla
    const attrler = Array.from(element.attributes)
      .map(a => `${a.name}="${a.value}"`)
      .join(" ");
    const attrStr = attrler ? ` ${attrler}` : "";

    // Çocuğu olmayan element
    if (element.children.length === 0) {
      const metin = element.textContent.trim();
      const metinGosterim = metin
        ? ` → "${metin.substring(0, 30)}${metin.length > 30 ? '...' : ''}"`
        : "";
      console.log(`${bosluk}<${nodeAdi}${attrStr}>${metinGosterim}`);
    } else {
      console.log(`${bosluk}<${nodeAdi}${attrStr}>`);
      // Çocukları recursive olarak yazdır
      for (const cocuk of element.children) {
        domAgaciYazdir(cocuk, girinti + 1);
      }
    }
  }
}

// Kullanım — sayfanın body ağacını yazdır
domAgaciYazdir(document.body);

// Örnek çıktı:
// <body>
//   <header class="site-header">
//     <nav>
//       <a href="/"> → "Ana Sayfa"
//       <a href="/hakkinda"> → "Hakkında"
//     <h1> → "Hoşgeldiniz"
//   <main id="content">
//     <article class="post">
//       <h2> → "İlk Yazım"
//       <p> → "Merhaba dünya, bu benim ilk..."

Bu fonksiyon, DOM ağacının yapısını anlamak ve debug için kullanışlıdır. children ile element node'larını gezer, nodeType ile tip kontrolü yapar, attributes ile attribute'ları okur — bu derste öğrendiğimiz kavramların pratikte bir arada kullanımını gösterir.


Özet

  • 🔹 DOM, tarayıcının HTML belgesini hafızasında temsil ettiği nesne ağacıdır. JavaScript, DOM aracılığıyla sayfayı okur ve değiştirir.

  • 🔹 DOM ağacında her şey bir Node'dur: elementler (ELEMENT_NODE), metinler (TEXT_NODE), yorumlar (COMMENT_NODE). Parent-child-sibling ilişkileriyle hiyerarşik yapı oluşturur.

  • 🔹 children sadece element node'ları, childNodes tüm node türlerini döndürür — elementlerle çalışırken her zaman `children` tercih edin.

  • 🔹 Rendering pipeline: HTML → DOM → CSSOM → Render Tree → LayoutPaintComposite. JavaScript'in DOM'u değiştirmesi bu hattın bir kısmını yeniden tetikler.

  • 🔹 Layout thrashing'den kaçının: DOM okuma ve yazma işlemlerini ayırın, toplu eklemelerde DocumentFragment kullanın, animasyonlarda transform/opacity tercih edin.

  • 🔹 Script'leri defer ile yükleyin — DOM hazır olduğunda çalışır, parse'ı bloklamaz. DOMContentLoaded event'i DOM'un hazır olduğunu garanti eder.