---
title: 'Tarayıcı ve Web Platformu'
source: 'https://academia.sh/tr/kurslar/tarayici-platformu'
language: tr
updated: '2026-08-17T04:40:45+00:00'
license: 'CC BY-SA 4.0'
lessons: 24
outcomes:
    - 'Belge ağacını ve olay modelini programlı olarak yönetir'
    - "Tarayıcı depolama ve gözlemci API'lerini yerinde kullanır"
    - 'Servis çalışanı ile çevrimdışı davranış kurar'
---

# Tarayıcı ve Web Platformu

Belge ağacının programlı yönetimi, üç evreli olay modeli, depolama ve gözlemci API'leri, web bileşenleri ile çevrimdışı davranış ve tarayıcı araçlarıyla tanı.

## Belge ve Olaylar

Düğüm ağacını okuyup değiştiren API, olayın yakalama–hedef–kabarcık yolculuğu, tek dinleyiciyle çok öge yönetimi ve varsayılan tarayıcı davranışına müdahale.

### DOM API'si

Source: https://academia.sh/tr/kurslar/tarayici-platformu/dom-apisi

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.

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.

```js
// 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.

```js
// 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.

```js
// 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: `dataset`
özelliği onları iki yönde de dizgi olarak yansıtır ve `value` ya da `checked`'te görülen türden
bir ayrışma üretmez.

#### 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.

```js
// 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.

### Olay Modeli

Source: https://academia.sh/tr/kurslar/tarayici-platformu/olay-modeli

Bir olayın kökten hedefe ve hedeften köke izlediği yol; yakalama, hedef ve kabarcık evreleri, dinleyici sırası, yayılımı durdurmanın iki biçimi ve dinleyici kimliği.

Önceki ders belgeyi okumayı ve değiştirmeyi kurdu. Değişikliği neyin tetikleyeceği açık
kalmıştı: bir sayfa kendiliğinden değişmez, kullanıcının ya da tarayıcının ürettiği bir
bildirime yanıt olarak değişir. Bu bildirime **olay (event)** denir.

Olayın hangi düğüme ait olduğu ilk bakışta bellidir — tıklanan düğme, yazılan alan. Asıl
soru şudur: olay yalnızca o düğüme mi bildirilir? Yanıt hayırdır ve bu dersin konusu,
olayın ağaç üzerinde izlediği tanımlı yoldur.

#### Olay Bir Nesnedir

Bir olay, tarayıcının ürettiği ve dinleyiciye argüman olarak verdiği bir nesnedir. Üç
alanı her olayda bulunur.

**Tür (type)** olayın adıdır: `click`, `input`, `submit`, `keydown`. Dinleyici bir türe
kaydolur; başka türdeki olaylar onu çağırmaz.

**Hedef (target)** olayın kaynaklandığı düğümdür. Tıklama olayında bu, imlecin altında
kalan en derin ögedir — satırın kendisine tıklanmış gibi görünse bile, hedef o satırdaki
düğmedir.

**Geçerli hedef (currentTarget)** dinleyicinin bağlı olduğu düğümdür. Bu ikisi yalnızca
dinleyici hedefin kendisine bağlandığında aynıdır; başka her durumda ayrışırlar ve bu
ayrım sonraki dersin tamamının dayanağıdır.

Dinleyicinin ne zaman çalıştığı, Eşzamansız JavaScript ve Çalışma Zamanı kursundaki Olay
Döngüsü dersinde tanımlanan modele bağlıdır: olay üretildiğinde dinleyici hemen çağrılmaz,
bir görev olarak kuyruğa alınır ve çağrı yığıtı boşaldığında çalıştırılır. Uzun süren bir
dinleyici bu yüzden yalnızca kendini geciktirmez, kuyruktaki bütün olayları geciktirir.

#### Yayılım Yolu

Bir olay üretildiğinde tarayıcı önce **yayılım yolunu (propagation path)** hesaplar: kök
düğümden hedefe kadar uzanan ata zinciri. Bu yol olay yayılmaya başlamadan önce belirlenir
ve yayılım sırasında ağaç değişse bile değişmez.

Yol boyunca her düğüm iki kez ziyaret edilir: bir kez kökten hedefe inerken, bir kez
hedeften köke çıkarken. Bu iki geçiş üç evre üretir.

**Yakalama evresi (capture phase)** kökten hedefin ebeveynine kadar iner. Atalara bağlı
ve yakalama kipinde kaydedilmiş dinleyiciler burada çağrılır.

**Hedef evresi (target phase)** hedefin kendisidir.

**Kabarcık evresi (bubble phase)** hedefin ebeveyninden köke çıkar. Atalara bağlı ve
kabarcık kipinde kaydedilmiş dinleyiciler burada çağrılır.

```js
// yayilim.mjs — uc evreli olay yayiliminin kucuk bir modeli
const oge = (ad, ...cocuklar) => {
  const d = { ad, cocuklar, ebeveyn: null, dinleyiciler: [] };
  for (const c of cocuklar) c.ebeveyn = d;
  return d;
};

const dinle = (dugum, tur, etiket, yakalamada = false) =>
  dugum.dinleyiciler.push({ tur, etiket, yakalamada });

// Hedeften koke yurunur, ters cevrilir: yol her zaman kokten hedefe dogrudur.
function yol(hedef) {
  const zincir = [];
  for (let d = hedef; d; d = d.ebeveyn) zincir.push(d);
  return zincir.reverse();
}

const EVRE_ADI = { 1: "yakalama", 2: "hedef", 3: "kabarcık" };

function yay(hedef, tur) {
  const zincir = yol(hedef);
  const cagrilar = [];

  const calistir = (dugum, evre, yakalamada) => {
    for (const d of dugum.dinleyiciler) {
      if (d.tur !== tur || d.yakalamada !== yakalamada) continue;
      cagrilar.push({ evre, dugum: dugum.ad, dinleyici: d.etiket });
    }
  };

  for (const dugum of zincir.slice(0, -1)) calistir(dugum, 1, true);   // yakalama
  calistir(hedef, 2, true);                                            // hedef
  calistir(hedef, 2, false);                                           // hedef
  for (const dugum of zincir.slice(0, -1).reverse()) calistir(dugum, 3, false); // kabarcık

  return cagrilar;
}

const dugme = oge("button.kaydet");
const satir = oge("tr.olcum", dugme);
const tablo = oge("table#gunluk", satir);
const ana = oge("main", tablo);
const govde = oge("body", ana);

dinle(govde, "click", "govde-yakalama", true);
dinle(ana, "click", "ana-yakalama", true);
dinle(tablo, "click", "tablo-kabarcik");
dinle(satir, "click", "satir-yakalama", true);
dinle(satir, "click", "satir-kabarcik");
dinle(dugme, "click", "dugme-1");
dinle(dugme, "click", "dugme-2", true);

console.log("yol:", yol(dugme).map((d) => d.ad).join(" > "));
console.log();
for (const c of yay(dugme, "click")) {
  console.log(String(c.evre).padEnd(2), EVRE_ADI[c.evre].padEnd(9), c.dugum.padEnd(14), c.dinleyici);
}
```

```
yol: body > main > table#gunluk > tr.olcum > button.kaydet

1  yakalama  body           govde-yakalama
1  yakalama  main           ana-yakalama
1  yakalama  tr.olcum       satir-yakalama
2  hedef     button.kaydet  dugme-2
2  hedef     button.kaydet  dugme-1
3  kabarcık  tr.olcum       satir-kabarcik
3  kabarcık  table#gunluk   tablo-kabarcik
```

Çıktı, istasyon sayfasındaki ölçüm tablosunda bir satırın kaydet düğmesine tıklandığında
ne olduğunu gösterir. Yedi dinleyici kayıtlıdır ve yedisi de çağrılır; çağrılma sırası
kayıt sırasına değil, düğümün yoldaki konumuna ve dinleyicinin evresine bağlıdır.

`table#gunluk` düğümüne bağlı `tablo-kabarcik` dinleyicisi kayıt sırasında üçüncüdür ama
çağrı sırasında sonuncudur. Bunun nedeni, kabarcık evresinin yolu ters yönde
dolaşmasıdır: hedefe en yakın ata önce, en uzak ata en sonra çağrılır.

#### Hedefteki Sıra

Çıktının dördüncü ve beşinci satırı bir ayrıntı taşır: `dugme-2` kayıtta sonuncu olmasına
karşın `dugme-1` den önce çağrılmıştır. Aradaki tek fark, `dugme-2` nin yakalama kipinde
kaydedilmiş olmasıdır.

Nedeni yolun iki kez dolaşılmasıdır. Hedef düğüm her iki geçişte de ziyaret edilir: inen
geçişte yakalama kipindeki dinleyicileri, çıkan geçişte kabarcık kipindekiler çağrılır.
Evre adı ikisinde de "hedef" olarak bildirilir; sıra ise geçişten gelir.

Bu davranış, aynı düğüme hem yakalama hem kabarcık kipinde dinleyici bağlanan durumlarda
şaşırtıcı olabilir. Kural sadedir: **aynı düğümde yakalama kipindeki dinleyiciler her
zaman önce çalışır**, kayıt sırasından bağımsız olarak. Aynı kipteki dinleyiciler ise
kendi aralarında kayıt sırasını korur.

#### Yayılımı Durdurmak

Bir dinleyici, olayın yoldaki yolculuğunu kesebilir. İki ayrı işlem vardır ve etkileri
farklıdır.

```js
// durdurma.mjs — yayilimi durdurmanin iki bicimi ve kabarmayan olay
const oge = (ad, ...cocuklar) => {
  const d = { ad, cocuklar, ebeveyn: null, dinleyiciler: [] };
  for (const c of cocuklar) c.ebeveyn = d;
  return d;
};
const dinle = (dugum, etiket, secenek = {}) =>
  dugum.dinleyiciler.push({ etiket, yakalamada: secenek.yakalamada ?? false, eylem: secenek.eylem });

function yol(hedef) {
  const zincir = [];
  for (let d = hedef; d; d = d.ebeveyn) zincir.push(d);
  return zincir.reverse();
}

function yay(hedef, { kabarir = true } = {}) {
  const zincir = yol(hedef);
  const olay = { durduruldu: false, hemenDurduruldu: false, cagrilanlar: [] };

  const calistir = (dugum, yakalamada) => {
    for (const d of dugum.dinleyiciler) {
      if (d.yakalamada !== yakalamada) continue;
      if (olay.hemenDurduruldu) return;      // ayni dugumdeki kalanlari da atlar
      olay.cagrilanlar.push(dugum.ad + "/" + d.etiket);
      d.eylem?.(olay);
    }
  };

  for (const d of zincir.slice(0, -1)) {
    if (olay.durduruldu) break;              // sonraki dugume gecilmez
    calistir(d, true);
  }
  if (!olay.durduruldu) calistir(hedef, true);
  if (!olay.durduruldu) calistir(hedef, false);
  if (kabarir) {
    for (const d of zincir.slice(0, -1).reverse()) {
      if (olay.durduruldu) break;
      calistir(d, false);
    }
  }
  return olay.cagrilanlar;
}

function agacKur(satirEylem) {
  const dugme = oge("button");
  const satir = oge("tr", dugme);
  const tablo = oge("table", satir);
  const govde = oge("body", tablo);
  dinle(govde, "govde-yakalama", { yakalamada: true });
  dinle(satir, "satir-yakalama", { yakalamada: true, eylem: satirEylem });
  dinle(dugme, "dugme-1");
  dinle(dugme, "dugme-2");
  dinle(satir, "satir-kabarcik");
  dinle(tablo, "tablo-kabarcik");
  return dugme;
}

console.log("engelsiz          :", yay(agacKur(undefined)).join(" → "));
console.log("stopPropagation   :", yay(agacKur((o) => { o.durduruldu = true; })).join(" → "));
console.log("kabarmayan olay   :", yay(agacKur(undefined), { kabarir: false }).join(" → "));

// stopImmediatePropagation ayni dugumdeki sonraki dinleyiciyi de engeller.
const dugme = oge("button");
const govde2 = oge("body", dugme);
dinle(govde2, "govde-kabarcik");
dinle(dugme, "dugme-1", { eylem: (o) => { o.hemenDurduruldu = true; o.durduruldu = true; } });
dinle(dugme, "dugme-2");
console.log("stopImmediate...  :", yay(dugme).join(" → "));
```

```
engelsiz          : body/govde-yakalama → tr/satir-yakalama → button/dugme-1 → button/dugme-2 → tr/satir-kabarcik → table/tablo-kabarcik
stopPropagation   : body/govde-yakalama → tr/satir-yakalama
kabarmayan olay   : body/govde-yakalama → tr/satir-yakalama → button/dugme-1 → button/dugme-2
stopImmediate...  : button/dugme-1
```

`stopPropagation`, olayın **sonraki düğüme** geçmesini engeller. Aynı düğüme bağlı diğer
dinleyiciler yine de çağrılır. İkinci satırda yakalama evresinde satırda durdurulan olay
hedefe hiç ulaşmamıştır — düğmenin kendi dinleyicileri çalışmamıştır.

`stopImmediatePropagation` buna ek olarak **aynı düğümdeki kalan dinleyicileri** de atlar.
Son satırda `dugme-2` çağrılmamıştır.

İkisi arasındaki seçim bir tasarım kararıdır. `stopPropagation` çağıran kod, ağacın
yukarısında ne olduğunu bilmeden onu iptal eder. Sayfanın başka bir yerinde belgenin
tamamına bağlanmış bir dinleyici — açık bir menüyü kapatan ya da bir ölçümü kaydeden —
sessizce devre dışı kalır. Bu nedenle yayılımı durdurmak, iki kod parçasının birbirini
bilmeden etkilediği bir bağımlılık üretir ve son çare olarak kullanılır.

#### Kabaran ve Kabarmayan Olaylar

Her olay kabarmaz. Çıktının üçüncü satırı bunu gösterir: kabarmayan bir olayda yakalama
ve hedef evreleri olduğu gibi çalışır, yalnızca kabarcık evresi atlanır.

Bu ayrım sık yanlış anlaşılır. Kabarmayan bir olay "yalnızca hedefe ulaşır" değildir;
atalarına yakalama kipinde bağlanmış dinleyiciler onu görür. Yalnızca kabarcık kipindeki
ata dinleyicileri görmez.

Kabarmayan olayların örnekleri belirli bir mantığa uyar: bir ögeye özgü olan ve ata
düğümler için anlamı olmayan bildirimler kabarmaz. Odak alma ve odak kaybetme olayları
kabarmaz; aynı bilgiyi kabaran bir çiftle veren ayrı olay türleri tanımlıdır. Bir görselin
yüklenmesi kabarmaz. Kullanıcı girdisinden doğan olaylar — tıklama, tuş basımı, girdi,
gönderim — kabarır.

Bir olayın kabarıp kabarmadığı olay nesnesinin `bubbles` alanından okunabilir; bu, belgeye
bakarak tahmin etmekten güvenilir bir yoldur.

#### Dinleyici Kimliği

Bir dinleyicinin kaydı üç bilgiyle tanımlanır: olay türü, işlev başvurusu ve yakalama
kipi. Aynı üçlüyle ikinci kez kayıt yapmak yeni bir dinleyici eklemez, yok sayılır. Buna
karşılık aynı işlev farklı kiple kaydedilirse iki ayrı dinleyici olur.

Kaldırma da aynı üçlüyle yapılır. Buradan yaygın bir hata çıkar: kayıt sırasında yerinde
tanımlanmış bir işlev, kaldırma sırasında tekrar yazıldığında **başka bir işlevdir** ve
eşleşme kurulamaz. Kaldırılabilir olması gereken dinleyicinin işlevi bir ada bağlanmalıdır.

Kaydın seçenekleri arasında iki tanesi bu sorunu kaynağında çözer. `once` seçeneği,
dinleyiciyi ilk çağrıdan sonra kendiliğinden kaldırır. `signal` seçeneği, Eşzamansız
JavaScript ve Çalışma Zamanı kursundaki İptal ve Zaman Aşımı dersinde tanımlanan iptal
işaretini kabul eder; işaret iptal edildiğinde o işarete bağlı bütün dinleyiciler tek
hamlede kaldırılır. Çok sayıda dinleyici kaydeden bir bileşenin temizliği bu yolla tek
satıra iner.

#### Özet

- Olay üretildiğinde önce yayılım yolu hesaplanır: kökten hedefe uzanan ata zinciri. Yol
  yayılım başlamadan sabitlenir.
- Yol iki kez dolaşılır ve üç evre üretir: yakalama (kökten hedefe), hedef, kabarcık
  (hedeften köke).
- Aynı düğümde yakalama kipindeki dinleyiciler kabarcık kipindekilerden önce çalışır; aynı
  kip içinde kayıt sırası korunur.
- `stopPropagation` olayın sonraki düğüme geçmesini, `stopImmediatePropagation` buna ek
  olarak aynı düğümdeki kalan dinleyicileri de engeller.
- Kabarmayan olaylarda yalnızca kabarcık evresi atlanır; yakalama ve hedef evreleri
  değişmez.
- Dinleyici kimliği tür, işlev başvurusu ve yakalama kipi üçlüsüdür; kaldırma aynı üçlüyü
  gerektirir, `once` ve `signal` seçenekleri bu gereği ortadan kaldırır.

#### Sonraki Adım

Bu ders olayın hedefe ulaşmadan ve ulaştıktan sonra atalarından geçtiğini gösterdi. Bu
yolculuk bir yük değil, bir olanaktır: hedefin atasına bağlanmış tek bir dinleyici, o
atanın altındaki bütün ögelerin olaylarını görebilir. Ölçüm tablosunun her satırına ayrı
dinleyici bağlamak yerine tabloya tek dinleyici bağlamak, hem kayıt sayısını hem de
sonradan eklenen satırların dinleyicisiz kalması sorununu ortadan kaldırır. Sonraki ders
bu tekniği, hedefi ata düğümden ayırt etme kuralıyla birlikte kurar.

### Olay Devri

Source: https://academia.sh/tr/kurslar/tarayici-platformu/olay-devri

Ata düğüme bağlanan tek dinleyiciyle çok sayıda ögenin olaylarını karşılamak; hedefi geçerli hedeften ayırt etme, en yakın eşleşen atayı bulma, kimliği işaretlemede taşıma ve devrin sınırları.

Önceki ders olayın hedefe ulaşmadan önce ve ulaştıktan sonra atalarından geçtiğini
gösterdi. Bu yolculuk bir yük değil bir olanaktır: bir atanın dinleyicisi, o atanın
altındaki bütün ögelerin olaylarını görür. Ölçüm tablosunun her satırına ayrı dinleyici
bağlamak yerine tabloya tek dinleyici bağlanabilir.

Bu tekniğin adı **olay devri (event delegation)** dir. Devrin işe yaraması tek bir
ayrıma dayanır: dinleyici ata düğüme bağlıdır, ama olayı üreten öge hedeftir. Dinleyici,
kendisine gelen olayın hangi alt ögeden geldiğini çözümlemek zorundadır.

#### İki Sorun

Doğrudan bağlama iki ayrı sorun üretir ve ikincisi daha sinsidir.

Birincisi **kayıt sayısıdır**. Yüz satırlık bir tabloda satır başına bir dinleyici, yüz
kayıt demektir. Her kayıt bellekte bir giriş tutar ve her kayıt, ögenin ağaçtan
çıkarılmasıyla temizlenmesi gereken bir bağ kurar.

İkincisi **yaşam süresidir**. Dinleyici bir ögeye kaydedildiği anda var olan ögeye
bağlanır. Sayfa yüklendikten sonra eklenen bir satır, kayıt sırasında ortada olmadığı
için dinleyicisiz kalır. Bu, DOM API'si dersindeki canlı koleksiyon ile durağan liste
ayrımının davranış tarafındaki karşılığıdır: durağan bir anda kurulan bağ, sonradan gelen
düğümleri kapsamaz.

Devir iki sorunu birden çözer. Dinleyici, satırlardan daha uzun yaşayan bir ataya —
tabloya — bağlanır. Satırlar gelip gitse de tablo yerinde durur.

#### Hedefi Çözümlemek

Devredilen dinleyicinin ilk işi, gelen olayın ilgilendiği bir ögeden gelip gelmediğine
karar vermektir. Burada iki işlem birbirine karıştırılır.

**Eşleşme sınaması** bir ögenin verilen seçiciye uyup uymadığını söyler; yalnız o ögeye
bakar. **En yakın ata araması** hedeften başlar, ata zincirini yukarı yürür ve seçiciye
uyan ilk düğümü döndürür.

Fark, tıklamanın nereye düştüğünde ortaya çıkar. Silme düğmesinin içinde bir simge ögesi
varsa, kullanıcı düğmeye bastığında hedef düğme değil simgedir. Yalnız hedefi sınayan kod
bu tıklamayı kaçırır.

```js
// enyakin.mjs — hedeften sinira dogru yuruyen es arama
const oge = (ad, ozn = {}, ...cocuklar) => {
  const d = { ad, ozn, cocuklar, ebeveyn: null };
  for (const c of cocuklar) c.ebeveyn = d;
  return d;
};

const adlandir = (d) =>
  d.ad + (d.ozn.id ? "#" + d.ozn.id : "") + (d.ozn.class ? "." + d.ozn.class : "");

const esler = (d, secici) => {
  if (secici.startsWith(".")) return (d.ozn.class ?? "").split(/\s+/).includes(secici.slice(1));
  if (secici.startsWith("#")) return d.ozn.id === secici.slice(1);
  return d.ad === secici;
};

// Hedeften baslar, sinira kadar yukari yurur; sinir da denenir, otesi denenmez.
const enYakin = (hedef, secici, sinir) => {
  for (let d = hedef; d; d = d.ebeveyn) {
    if (esler(d, secici)) return d;
    if (d === sinir) break;
  }
  return null;
};

const simge = oge("span", { class: "simge" });
const dugme = oge("button", { class: "sil" }, simge);
const satir = oge("tr", { class: "olcum", "data-kod": "T-04" }, oge("td", {}), oge("td", {}, dugme));
const tablo = oge("table", { id: "gunluk" }, satir);

console.log("hedef         :", adlandir(simge));
console.log("gecerli hedef :", adlandir(tablo));
console.log("esler(hedef, '.sil')   :", esler(simge, ".sil"));
console.log("enYakin('.sil')        :", adlandir(enYakin(simge, ".sil", tablo)));
console.log("enYakin('tr')          :", adlandir(enYakin(simge, "tr", tablo)), enYakin(simge, "tr", tablo).ozn["data-kod"]);
console.log("enYakin('body')        :", enYakin(simge, "body", tablo));
```

```
hedef         : span.simge
gecerli hedef : table#gunluk
esler(hedef, '.sil')   : false
enYakin('.sil')        : button.sil
enYakin('tr')          : tr.olcum T-04
enYakin('body')        : null
```

Üçüncü satır sorunu gösterir: hedefin kendisi silme düğmesi değildir. Dördüncü satır
çözümü gösterir: yukarı yürüyen arama düğmeyi bulur. Beşinci satır bir adım daha gider ve
düğmenin ait olduğu satırı bulur; ölçümün kodu oradan okunur.

Son satır **sınır** kavramını gösterir. Arama geçerli hedefte durur. Bu sınır olmadan
arama belgenin köküne kadar çıkar ve dinleyicinin kapsamı dışındaki bir düğümü döndürerek
başka bir bileşenin ögesi üzerinde işlem yapılmasına yol açabilir. Tarayıcı API'sinde bu
işlemlerin adı `matches` ve `closest` tir; `closest` sınır almadığı için sonucun geçerli
hedefin altında kaldığı ayrıca sınanır.

#### Devri Kurmak

İki yaklaşımın davranış farkı, sonradan eklenen bir satırda görünür.

```js
// devir.mjs — her ogeye baglama ile tek dinleyiciyle devir arasindaki fark
const oge = (ad, ozn = {}, ...cocuklar) => {
  const d = { ad, ozn, cocuklar, ebeveyn: null, dinleyiciler: [] };
  for (const c of cocuklar) c.ebeveyn = d;
  return d;
};
const esler = (d, s) =>
  s.startsWith(".") ? (d.ozn.class ?? "").split(/\s+/).includes(s.slice(1)) : d.ad === s;
const enYakin = (hedef, s, sinir) => {
  for (let d = hedef; d; d = d.ebeveyn) { if (esler(d, s)) return d; if (d === sinir) break; }
  return null;
};
// Kabarcik evresi: hedeften koke dogru her dugumun dinleyicileri cagrilir.
const tikla = (hedef) => {
  for (let d = hedef; d; d = d.ebeveyn)
    for (const dinleyici of d.dinleyiciler) dinleyici({ hedef, gecerliHedef: d });
};
const dinleyiciSayisi = (kok) =>
  kok.dinleyiciler.length + kok.cocuklar.reduce((t, c) => t + dinleyiciSayisi(c), 0);

const satirKur = (kod) =>
  oge("tr", { class: "olcum", "data-kod": kod }, oge("td", {}), oge("td", {}, oge("button", { class: "sil" })));
const tabloKur = () =>
  oge("table", { id: "gunluk" },
    oge("tr", { class: "baslik" }, oge("th", {})),
    satirKur("T-01"), satirKur("T-02"), satirKur("T-03"));

const dugmeler = (kok) =>
  [kok, ...kok.cocuklar.flatMap((c) => dugmeler(c))].filter((d) => esler(d, ".sil"));

let silinen = [];
const sil = (satir) => silinen.push(satir.ozn["data-kod"]);

// Birinci yol: her silme dugmesine ayri dinleyici.
const a = tabloKur();
for (const b of dugmeler(a)) b.dinleyiciler.push(({ gecerliHedef }) => sil(gecerliHedef.ebeveyn.ebeveyn));
console.log("dogrudan baglama, dinleyici:", dinleyiciSayisi(a));
a.cocuklar.push(Object.assign(satirKur("T-04"), { ebeveyn: a })); // sonradan gelen satir
silinen = []; tikla(dugmeler(a)[0]); console.log("  T-01 dugmesi:", silinen.length ? silinen : "islem yok");
silinen = []; tikla(dugmeler(a)[3]); console.log("  T-04 dugmesi:", silinen.length ? silinen : "islem yok");

// Ikinci yol: tabloya tek dinleyici; hedef her cagrida yeniden cozumlenir.
const b = tabloKur();
b.dinleyiciler.push(({ hedef, gecerliHedef }) => {
  const dugme = enYakin(hedef, ".sil", gecerliHedef);
  if (dugme === null) return;
  sil(enYakin(dugme, "tr", gecerliHedef));
});
console.log("devir, dinleyici          :", dinleyiciSayisi(b));
b.cocuklar.push(Object.assign(satirKur("T-04"), { ebeveyn: b }));
silinen = []; tikla(dugmeler(b)[0]); console.log("  T-01 dugmesi:", silinen.length ? silinen : "islem yok");
silinen = []; tikla(dugmeler(b)[3]); console.log("  T-04 dugmesi:", silinen.length ? silinen : "islem yok");
silinen = []; tikla(b.cocuklar[0].cocuklar[0]); console.log("  baslik hucresi:", silinen.length ? silinen : "islem yok");
```

```
dogrudan baglama, dinleyici: 3
  T-01 dugmesi: [ 'T-01' ]
  T-04 dugmesi: islem yok
devir, dinleyici          : 1
  T-01 dugmesi: [ 'T-01' ]
  T-04 dugmesi: [ 'T-04' ]
  baslik hucresi: islem yok
```

Doğrudan bağlamada dinleyici sayısı satır sayısıyla birlikte artar ve sonradan eklenen
satır sessiz kalır. Devirde tek kayıt vardır ve sonradan eklenen satır ilk satırdan farksız
çalışır. Farkın kaynağı zamanlamadır: doğrudan bağlamada eşleşme **kayıt anında**, devirde
**olay anında** kurulur.

Son satır devrin ikinci gereğini gösterir. Tabloya bağlı dinleyici, tablonun altındaki her
tıklamayı görür — başlık hücresini, boş bir hücreyi, satır arasındaki boşluğu. Eşleşme
bulunamadığında dinleyici hiçbir şey yapmadan döner. Bu erken çıkış, devredilen her
dinleyicinin ilk satırıdır.

#### Kimliği İşaretlemede Taşımak

Devredilen dinleyici, olayın hangi kayda ait olduğunu bilmek zorundadır. Bu bilgiyi bir
dizide sıra numarasıyla tutmak kırılgandır: satırlar sıralandığında ya da süzüldüğünde
sıra numarası kayar.

Sağlam yol, kimliği ögenin kendisinde taşımaktır. Web'in Temelleri ve HTML kursundaki
Veri Öznitelikleri dersinde tanımlanan `data-` öznitelikleri bu iş için vardır: değer
işaretlemede durur, öge ağaçta hareket etse de değeriyle birlikte hareket eder. Yukarıdaki
örnekte satırın ölçüm kodu bu yolla okunmuştur.

Aynı öznitelikler eylem türünü de taşıyabilir. Bir satırda silme, düzenleme ve
işaretleme düğmeleri varsa, her biri için ayrı bir seçici sınamak yerine düğmelerin
ortak bir özniteliğinde eylem adı tutulur; dinleyici tek arama yapar ve eylemi bir
eşlemeden çağırır. Bu, dinleyicinin uzunluğunu satır sayısından da eylem sayısından da
bağımsız kılar.

#### Devrin Sınırları

Devir her olay türünde işlemez ve iki sınırı vardır.

**Kabarmayan olaylar devredilemez.** Önceki derste tanımlandığı gibi odak alma ve odak
kaybetme olayları kabarmaz; ataya bağlı kabarcık kipindeki bir dinleyici onları görmez.
Bu olayların kabaran karşılıkları ayrı türler olarak tanımlıdır ve devir onlarla kurulur.
İkinci yol, dinleyiciyi ataya **yakalama kipinde** kaydetmektir: yakalama evresi kabarmayan
olaylarda da işlediği için ata düğüm olayı görür. İki yolun seçimi, olayın hedefe
ulaşmadan mı ulaştıktan sonra mı işlenmesi gerektiğine bağlıdır.

**Yayılımı durduran ara dinleyiciler devri sessizce bozar.** Tabloya devredilmiş bir
dinleyici, satırın içindeki bir dinleyici `stopPropagation` çağırdığında hiç
çağrılmaz. Önceki derste yayılımı durdurmanın son çare olduğu söylenmişti; devir
kurulmuş bir ağaçta bu, uzaktan etki eden bir kesintiye dönüşür.

Bir üçüncü nokta sınır değil ama tasarım kararıdır: dinleyicinin bağlandığı ata ne kadar
yukarıdaysa kapsamı o kadar geniştir. Belgenin tamamına bağlanan bir dinleyici sayfadaki
her tıklamada çalışır. Devir, ilgilenilen ögelerin **ortak en yakın atasına** kurulur;
tablonun olayları tabloya, formun olayları forma devredilir.

#### Özet

- Olay devri, ilgilenilen ögelerin bir atasına tek dinleyici bağlayıp hedefi olay anında
  çözümlemektir; dinleyici sayısını öge sayısından bağımsız kılar.
- Devir, kayıt anında var olmayan ögeleri de kapsar; eşleşme kayıt anında değil olay
  anında kurulduğu için sonradan eklenen düğümler dinleyicisiz kalmaz.
- Hedef, seçiciye uyan en yakın ataya çözümlenir; yalnız hedefi sınayan kod, iç içe
  ögelerden gelen tıklamaları kaçırır.
- Arama geçerli hedefte durdurulur; sınırsız arama dinleyicinin kapsamı dışındaki bir
  düğümü döndürebilir.
- Kayıt kimliği `data-` öznitelikleriyle ögenin kendisinde taşınır; sıra numarasına
  dayanan eşleme sıralama ve süzme sonrasında bozulur.
- Kabarmayan olaylar kabarcık kipiyle devredilemez; yakalama kipi ya da kabaran karşılık
  türü kullanılır. Ara bir dinleyicinin yayılımı durdurması devri sessizce iptal eder.

#### Sonraki Adım

Bu derse kadar dinleyiciler yalnızca **eklediler**: olay geldi, kod çalıştı, sayfaya bir
davranış katıldı. Oysa tarayıcının kendi davranışları da vardır ve bunlar dinleyici
olmadan da işler. Bağlantıya tıklamak gezinme başlatır, gönderim düğmesi formu gönderir,
onay kutusu işaretini değiştirir, boşluk tuşu sayfayı kaydırır. Bir dinleyici bu
davranışların yerine kendi davranışını koymak istediğinde, tarayıcının yapacağı işi iptal
etmesi gerekir. Sonraki ders bu iptalin nasıl istendiğini, hangi olaylarda mümkün
olduğunu ve yayılımı durdurmakla neden karıştırılmaması gerektiğini inceler.

### Varsayılan Davranış ve İptal

Source: https://academia.sh/tr/kurslar/tarayici-platformu/varsayilan-davranis-ve-iptal

Tarayıcının olaylara verdiği yerleşik yanıt ve onu iptal etme isteği; iptal edilebilirlik koşulu, iptalin yayılımı durdurmakla karışmaması, edilgen dinleyicinin kısıtı ve iptalin ne zaman meşru olduğu.

Önceki derslerde dinleyiciler yalnızca ekledi: olay geldi, kod çalıştı, sayfaya bir
davranış katıldı. Oysa tarayıcının kendi davranışları da vardır ve hiçbir dinleyici
olmadan işlerler. Bağlantıya tıklamak gezinme başlatır, gönderim düğmesi formu gönderir,
onay kutusu işaretini değiştirir, boşluk tuşu sayfayı kaydırır.

Bir dinleyici bu davranışların yerine kendi davranışını koymak istediğinde, tarayıcının
yapacağı işi iptal etmesi gerekir. Bu ders iptalin nasıl istendiğini, hangi koşullarda
kabul edildiğini ve neden yayılımı durdurmakla aynı şey olmadığını kurar.

#### Varsayılan Eylem

Bir olayın **varsayılan eylemi (default action)**, tarayıcının o olaya verdiği yerleşik
yanıttır. Belgede yazılı değildir, biçemde de değildir; işaretlemenin anlamından gelir.
Bağlantı ögesinin anlamı "başka bir kaynağa götürür" olduğu için tıklandığında gezinme
başlar. Gönderim düğmesinin anlamı "formu gönderir" olduğu için tıklandığında giriş
listesi hazırlanır.

Varsayılan eylemin iki özelliği kritiktir.

Birincisi **sırasıdır**. Varsayılan eylem, olayın yayılımı bittikten sonra çalışır.
Yayılım yolundaki herhangi bir dinleyici — hedeftekiler kadar atalardakiler de — eylemi
iptal edebilir. Ata düğümün kabarcık evresindeki dinleyicisi, hedefte başlamış bir
gezinmeyi durdurabilir.

İkincisi **zincirlenebilirliğidir**. Bazı varsayılan eylemler yeni bir olay üretir. Bir
etikete tıklamak, bağlı olduğu form alanında bir tıklama olayı doğurur; o tıklamanın da
kendi varsayılan eylemi vardır. Bu nedenle tek bir kullanıcı hareketi birden çok olay ve
birden çok varsayılan eylem üretebilir; zincirin hangi halkasında iptal edildiği sonucu
belirler.

#### İptal İsteği

Dinleyici, olay nesnesi üzerinden iptal ister. İstek her zaman kabul edilmez: olayın
**iptal edilebilir (cancelable)** olması gerekir. Kaydırma bildirimi gibi bazı olaylar
tarayıcı işi zaten yaptıktan sonra üretilir ve iptal edilemez; istek sessizce yok sayılır.

```js
// varsayilan.mjs — yayilim, iptal ve varsayilan eylemin sirasi
const oge = (ad, ...cocuklar) => {
  const d = { ad, cocuklar, ebeveyn: null, dinleyiciler: [] };
  for (const c of cocuklar) c.ebeveyn = d;
  return d;
};
const dinle = (dugum, etiket, eylem, edilgen = false) =>
  dugum.dinleyiciler.push({ etiket, eylem, edilgen });

function yay(hedef, { tur, iptalEdilebilir, varsayilanEylem }) {
  const zincir = [];
  for (let d = hedef; d; d = d.ebeveyn) zincir.push(d);

  const olay = { tur, iptalEdilebilir, varsayilanOnlendi: false, durduruldu: false };
  const gunluk = [];
  olay.varsayilaniOnle = function () {
    if (this.edilgenDinleyicide) return gunluk.push("  [yok sayildi] edilgen dinleyici");
    if (olay.iptalEdilebilir === false) return gunluk.push("  [yok sayildi] iptal edilemeyen olay");
    olay.varsayilanOnlendi = true;
  };

  for (const dugum of zincir) {          // kabarcik evresi: hedeften koke
    if (olay.durduruldu) break;
    for (const d of dugum.dinleyiciler) {
      olay.edilgenDinleyicide = d.edilgen;
      gunluk.push(`  ${dugum.ad}/${d.etiket} (onlendi=${olay.varsayilanOnlendi})`);
      d.eylem?.(olay);
    }
  }
  gunluk.push(olay.varsayilanOnlendi ? `  varsayilan eylem: iptal` : `  varsayilan eylem: ${varsayilanEylem}`);
  return gunluk;
}

function agac(dinleyiciKur) {
  const bag = oge("a");
  const gezinti = oge("nav", bag);
  oge("main", gezinti);
  dinleyiciKur(bag, gezinti);
  return bag;
}
const TIKLAMA = { tur: "click", iptalEdilebilir: true, varsayilanEylem: "gezinme" };

const durumlar = [
  ["engelsiz", TIKLAMA, (bag, nav) => { dinle(bag, "bag"); dinle(nav, "nav"); }],
  ["varsayilaniOnle", TIKLAMA, (bag, nav) => {
    dinle(bag, "bag", (o) => o.varsayilaniOnle()); dinle(nav, "nav");
  }],
  ["stopPropagation", TIKLAMA, (bag, nav) => {
    dinle(bag, "bag", (o) => { o.durduruldu = true; }); dinle(nav, "nav");
  }],
  ["iptal edilemeyen olay", { tur: "scroll", iptalEdilebilir: false, varsayilanEylem: "kaydirma" },
    (bag) => { dinle(bag, "bag", (o) => o.varsayilaniOnle()); }],
  ["edilgen dinleyici", { tur: "wheel", iptalEdilebilir: true, varsayilanEylem: "kaydirma" },
    (bag) => { dinle(bag, "bag", (o) => o.varsayilaniOnle(), true); }],
];

for (const [ad, olayTanimi, kur] of durumlar) {
  console.log(ad);
  for (const satir of yay(agac(kur), olayTanimi)) console.log(satir);
}
```

```
engelsiz
  a/bag (onlendi=false)
  nav/nav (onlendi=false)
  varsayilan eylem: gezinme
varsayilaniOnle
  a/bag (onlendi=false)
  nav/nav (onlendi=true)
  varsayilan eylem: iptal
stopPropagation
  a/bag (onlendi=false)
  varsayilan eylem: gezinme
iptal edilemeyen olay
  a/bag (onlendi=false)
  [yok sayildi] iptal edilemeyen olay
  varsayilan eylem: kaydirma
edilgen dinleyici
  a/bag (onlendi=false)
  [yok sayildi] edilgen dinleyici
  varsayilan eylem: kaydirma
```

İkinci durumda hedefteki dinleyici iptal ister; olay yoluna devam eder ve ata düğümdeki
dinleyici `onlendi=true` değerini okur. Tarayıcı API'sinde bu bayrağın adı
`defaultPrevented` tir. Yayılımın devam etmesi bir kusur değil, tasarımın kendisidir: yol
üzerindeki sonraki dinleyiciler kararı görebilir ve kendi davranışlarını ona göre
ayarlayabilir.

#### İptal ile Durdurmayı Ayırmak

Üçüncü durum bu dersin en sık karıştırılan noktasını gösterir. Yayılım durdurulmuştur —
ata dinleyicisi hiç çağrılmamıştır — ama gezinme yine de olmuştur.

İki işlem **birbirinden bağımsızdır**:

- Yayılımı durdurmak, olayın **başka dinleyicilere** ulaşmasını engeller. Tarayıcının
  kendi davranışına dokunmaz.
- İptal istemek, **tarayıcının davranışını** engeller. Olayın yoluna devam etmesini
  engellemez.

Buradan iki yanlış çıkar. Birincisi, gezinmeyi durdurmak isteyip yayılımı durduran kod
gezinmeyi durduramaz. İkincisi, yalnızca kendi işini yapmak isteyip iptal ile birlikte
yayılımı da durduran kod, sayfanın başka yerlerindeki devredilmiş dinleyicileri sessizce
devre dışı bırakır. İki işlem ayrı ayrı istenmelidir ve çoğu durumda yalnızca biri
gereklidir.

#### Edilgen Dinleyici

Son durum farklı bir kısıt gösterir. Kaydırma ile ilgili olaylarda tarayıcı bir ikilemle
karşılaşır: dinleyici iptal edebilir mi, edemez mi bilmeden kaydırmayı başlatamaz. Bu
nedenle dinleyicinin bitmesini bekler; uzun süren bir dinleyici parmağın altındaki
sayfayı bekletir.

**Edilgen dinleyici (passive listener)** bu belirsizliği kaldırır. Kayıt sırasında verilen
bir seçenekle dinleyici "iptal istemeyeceğim" diye bildirir; tarayıcı kaydırmayı
dinleyiciyi beklemeden başlatır. Bildirime aykırı olarak iptal istenirse istek yok
sayılır.

Buradan pratik bir kural çıkar: kaydırmayı gözleyen ama değiştirmeyen dinleyiciler
edilgen kaydedilir. Kaydırmanın yerine kendi davranışını koyan bir dinleyici edilgen
olamaz ve bekletme maliyetini kabul eder.

#### İptalin İnce Noktaları

**Bazı olaylarda iptal, izin anlamına gelir.** Sürükle bırak işleminde bir ögenin
üzerinden geçilirken üretilen olayın varsayılan eylemi "bırakmayı reddet" tir; iptal
edilmediği sürece bırakma gerçekleşmez. Burada iptal istemek davranışı engellemez,
tersine açar. Bu nedenle "iptal her zaman engellemektir" varsayımı yanlıştır; her olayın
varsayılan eyleminin ne olduğu ayrıca bilinmelidir.

**İptal, durumu geri almayabilir.** Onay kutusuna tıklandığında işaret değişimi olay
yayılmadan önce yapılır; iptal, değişimi geri alır. Dinleyici içinde okunan güncel değer
bu nedenle iptalden sonra geçerli olmayabilir.

**İptal edilmiş bir eylemin yerine bir şey konmalıdır.** Formun gönderimini iptal edip
veriyi kendi kodunda gönderen bir sayfa doğru davranır. Gönderimi iptal edip yerine
hiçbir şey koymayan sayfa, kullanıcının işini görünür bir neden olmadan bitirir. Aynı
ilke bağlantılar için de geçerlidir: gezinmeyi iptal eden kod, kullanıcının orta tuşla
yeni sekmede açma, adresi kopyalama ve bağlantının nereye gittiğini görme olanağını da
elinden alır.

**Klavye ve erişilebilirlik davranışları iptal edilmez.** Sekme tuşunun odak taşıma
davranışını iptal eden bir sayfa, klavyeyle kullanılamaz hâle gelir. Aynı biçimde metin
seçimini, sağ tuş menüsünü ve yakınlaştırmayı iptal etmek, kullanıcının denetimini
kaldırır. Bu davranışlar ancak yerlerine eşdeğer bir yol konduğunda iptal edilir; bunun
sınırları Odak ve Klavye Etkileşimi dersinde belirlenir.

#### İptalin Geri Alınamaması

Bayrağın okunabilir olması, değiştirilebilir olduğu anlamına gelmez. İptali geri alan bir
işlem yoktur. Yol üzerindeki ilk iptal isteği kararı kesinleştirir; sonraki dinleyiciler
yalnızca kararı görür.

Bunun tasarım sonucu şudur: iptal kararını, davranışın **sahibi** olan dinleyici vermelidir.
Ölçüm formunun gönderimini iptal edecek kod formun kendi dinleyicisidir; forma dışarıdan
bakan genel bir dinleyicinin kararı, formun kendi mantığını görmeden verilmiş olur.

Aynı kesinlik ters yönde de geçerlidir. Bir dinleyici yayılımın erken bir noktasında
koşulsuz iptal isterse, sonraki dinleyicilerin davranışı geri getirmesi mümkün değildir;
tek çare iptal koşulunu o dinleyicinin içine taşımaktır. Koşul dışarıda kaldıkça iptal,
davranışı sessizce ve kalıcı olarak kapatan bir anahtara dönüşür.

Sayfadan ayrılma bu kuralın en görünür örneğidir. Kaydedilmemiş değişikliği olan bir
sayfa, ayrılma öncesinde üretilen olayı iptal ederek tarayıcıdan onay istemesini
sağlayabilir. Onayın metnini ve biçimini sayfa belirlemez; tarayıcı belirler ve genellikle
kullanıcının sayfayla önceden etkileşmiş olmasını koşul sayar. Kural aynıdır: iptal
isteği bir öneridir, tarayıcının kabul koşulları dışında karşılık bulmaz.

#### Özet

- Varsayılan eylem, tarayıcının bir olaya verdiği ve işaretlemenin anlamından gelen
  yerleşik yanıttır; yayılım bittikten sonra çalışır.
- Yayılım yolundaki her dinleyici iptal isteyebilir; iptal isteği yalnızca olay iptal
  edilebilir olduğunda kabul edilir, aksi durumda sessizce yok sayılır.
- İptal ile yayılımı durdurmak bağımsız işlemlerdir: biri tarayıcının davranışını, diğeri
  olayın diğer dinleyicilere ulaşmasını engeller.
- İptal edildikten sonra da olay yoluna devam eder; sonraki dinleyiciler kararı bir bayrak
  üzerinden okuyabilir ama geri alamaz.
- Edilgen dinleyici iptal istemeyeceğini önceden bildirir; tarayıcı kaydırmayı beklemeden
  başlatır ve aykırı istek yok sayılır.
- Bazı olaylarda iptal engelleme değil izin anlamına gelir; iptal edilen her davranışın
  yerine eşdeğer bir yol konmalıdır.

#### Sonraki Adım

Bu ders, iptal edilmemesi gereken davranışlar arasında klavyeyi ayrı tuttu ve gerekçeyi
sonraya bıraktı. Gerekçe şudur: sayfayla etkileşimin tamamı işaretleme aygıtıyla
kurulmaz. Kullanıcı sekme tuşuyla ögeler arasında dolaşır, boşlukla düğmeye basar, ok
tuşlarıyla seçenekler arasında geçer; ekran okuyucu da aynı yolu izler. Bu dolaşımın
merkezinde tek bir kavram vardır: belgede her an en çok bir öge girdiyi alır. Sonraki
ders bu ögenin nasıl belirlendiğini, sıranın neye göre kurulduğunu ve klavye olaylarının
hangi bilgiyi taşıdığını inceler.

### Odak ve Klavye Etkileşimi

Source: https://academia.sh/tr/kurslar/tarayici-platformu/odak-ve-klavye-etkilesimi

Girdiyi alan tek ögenin belirlenmesi; odaklanabilirlik koşulları, sekme sırasının hesabı, odak olaylarının kabarma davranışı, tuş olaylarının taşıdığı bilgi ve grup içinde tek durak bırakan gezinme düzeni.

Önceki ders, klavye davranışlarının iptal edilmemesi gereken davranışlar arasında
olduğunu söyledi. Gerekçe şudur: sayfayla etkileşimin tamamı işaretleme aygıtıyla
kurulmaz. Kullanıcı sekme tuşuyla ögeler arasında dolaşır, boşlukla düğmeye basar, ok
tuşlarıyla seçenekler arasında geçer; ekran okuyucu da aynı yolu izler.

Bu dolaşımın merkezinde tek bir kavram vardır: belgede her an en çok bir öge girdiyi
alır. O ögenin adı **etkin öge (active element)** dir ve klavyeden gelen olayların hedefi
odur. Bu ders etkin ögenin nasıl belirlendiğini ve klavye olaylarının hangi bilgiyi
taşıdığını kurar.

#### Odaklanabilirlik

**Odak (focus)**, girdiyi alma yetkisidir. Her öge odak alamaz. Bir ögenin odak
alabilmesi üç koşula bağlıdır.

Birincisi **doğal odaklanabilirliktir**. Bağlantılar, düğmeler, form alanları ve
denetimler odak alır; bunun için ek bir bildirim gerekmez. Metin taşıyan bir bölüm ya da
bir listeye odak, ancak açıkça istendiğinde verilir.

İkincisi **görünürlüktür**. Belgeden gizlenmiş bir öge odak alamaz; gizli bir ögeye odak
vermeye çalışan kod sessizce başarısız olur. Bu, açılır bir panelin kapanma anında
odağın kaybolmasının kaynağıdır.

Üçüncüsü **etkinliktir**. Devre dışı bırakılmış bir denetim odak almaz; klavye
kullanıcısı onu hiç görmez. Buradan bir tasarım sonucu çıkar: bir düğmeyi devre dışı
bırakmak, onunla ilgili hata iletisini de erişilemez kılabilir. Nedeni ögenin üzerinde
duramamaktır.

Odak alan ögenin görsel bir işareti olması zorunludur. WCAG 2.4.7 ölçütü, odaklanmış
ögenin görünür bir göstergesi olmasını gerektirir. Odak göstergesini biçemden kaldıran
kod, göreni olmayan bir imleçle çalışmaya benzer bir durum üretir.

#### Sekme Sırası

Sekme tuşu odağı belge sırasına göre taşır. Bu sıra, `tabindex` özniteliğiyle
değiştirilebilir ve değerin işareti üç farklı davranış üretir.

```js
// sekme.mjs — sekme sirasinin belge sirasindan ve tabindex degerinden turetilmesi
const ogeler = [
  { ad: "a.atlama-bagi",      tabindex: null, dogalOdaklanir: true,  gizli: false, devreDisi: false },
  { ad: "input#suzgec",       tabindex: null, dogalOdaklanir: true,  gizli: false, devreDisi: false },
  { ad: "button#suz",         tabindex: null, dogalOdaklanir: true,  gizli: false, devreDisi: false },
  { ad: "button#disaAktar",   tabindex: 1,    dogalOdaklanir: true,  gizli: false, devreDisi: false },
  { ad: "div#ozet",           tabindex: -1,   dogalOdaklanir: false, gizli: false, devreDisi: false },
  { ad: "span.rozet",         tabindex: null, dogalOdaklanir: false, gizli: false, devreDisi: false },
  { ad: "button.sil[T-01]",   tabindex: null, dogalOdaklanir: true,  gizli: false, devreDisi: false },
  { ad: "button.sil[T-02]",   tabindex: null, dogalOdaklanir: true,  gizli: false, devreDisi: true  },
  { ad: "button#gonder",      tabindex: null, dogalOdaklanir: true,  gizli: true,  devreDisi: false },
  { ad: "div.satir-kabi",     tabindex: 0,    dogalOdaklanir: false, gizli: false, devreDisi: false },
];

const odaklanabilir = (o) =>
  !o.gizli && !o.devreDisi && (o.dogalOdaklanir || o.tabindex !== null);
const siradaYerAlir = (o) => odaklanabilir(o) && (o.tabindex === null ? true : o.tabindex >= 0);

const sekmeSirasi = (liste) => {
  const belgeSirasi = liste.map((o, i) => ({ ...o, i })).filter(siradaYerAlir);
  const pozitif = belgeSirasi.filter((o) => (o.tabindex ?? 0) > 0)
    .sort((a, b) => a.tabindex - b.tabindex || a.i - b.i);
  const sifir = belgeSirasi.filter((o) => (o.tabindex ?? 0) === 0);
  return [...pozitif, ...sifir];
};

console.log("sekme sirasi:");
for (const [n, o] of sekmeSirasi(ogeler).entries())
  console.log(`  ${n + 1}. ${o.ad.padEnd(18)} tabindex=${o.tabindex ?? "yok"}`);

console.log("sirada olmayanlar:");
for (const o of ogeler.filter((o) => !siradaYerAlir(o))) {
  const neden = o.gizli ? "gizli" : o.devreDisi ? "devre disi"
    : o.tabindex === -1 ? "tabindex=-1 (yalniz programla odaklanir)" : "odaklanabilir degil";
  console.log(`  ${o.ad.padEnd(18)} ${neden}`);
}
```

```
sekme sirasi:
  1. button#disaAktar   tabindex=1
  2. a.atlama-bagi      tabindex=yok
  3. input#suzgec       tabindex=yok
  4. button#suz         tabindex=yok
  5. button.sil[T-01]   tabindex=yok
  6. div.satir-kabi     tabindex=0
sirada olmayanlar:
  div#ozet           tabindex=-1 (yalniz programla odaklanir)
  span.rozet         odaklanabilir degil
  button.sil[T-02]   devre disi
  button#gonder      gizli
```

Üç kural çıktıda görünür.

**Sıfır değeri ögeyi sıraya sokar, yerini değiştirmez.** Doğal olarak odak almayan bir
kapsayıcı böylece sekme durağı olur ve belge sırasındaki yerini korur.

**Eksi bir değeri ögeyi sıradan çıkarır ama odaklanabilir bırakır.** Sekme tuşu üzerinden
geçmez; program odağı oraya taşıyabilir. Bir hata özetine ya da açılan panele odak vermek
bu değerle mümkün olur.

**Artı değerler sırayı bozar.** Çıktının ilk satırı bunu gösterir: dışa aktarma düğmesi
belgede dördüncü sıradadır, sekme sırasında birincidir. Sayfanın atlama bağı ikinci sıraya
düşmüştür. Artı değerler belgedeki bütün diğer duraklardan önce gelir; sayfaya sonradan
eklenen her bileşen bu sıralamayı yeniden düşünmek zorunda kalır. WCAG 2.4.3 ölçütünün
istediği anlamlı sıra, işaretlemenin sırasını doğru kurmakla sağlanır; artı değerlerle
değil.

#### Odak Olayları ve Programlı Odak

Odak değişimi dört olay üretir ve bunlar iki çift hâlinde tanımlıdır. Olay Modeli
dersinde belirtildiği gibi odak alma ve odak kaybetme olayları **kabarmaz**; aynı bilgiyi
veren kabaran bir çift ayrıca tanımlıdır. Bir formdaki bütün alanların odak değişimini tek
dinleyiciyle izlemek isteyen kod, kabaran çifti kullanmak ya da yakalama kipine kaydolmak
zorundadır; Olay Devri dersinde konan kural burada işler.

Odak olayları, odağı **bırakan** ve **alan** ögeyi birlikte bildirir. Bir alandan
çıkıldığında odağın nereye gittiği bu bilgiyle okunur ve iki durum ayırt edilir: odak
formun içinde başka bir alana geçmiştir ya da formun tamamından çıkılmıştır. Alan
doğrulamasını yalnızca ikinci durumda çalıştırmak, kullanıcıyı her alan geçişinde hata
iletisiyle karşılamaktan kurtarır.

Odağı program taşıdığında iki sorumluluk doğar. Birincisi, odak taşınan ögenin
görünmesidir; tarayıcı odaklanan ögeyi görünür alana kaydırır ve bu kaydırma bir sayfa
sıçraması olarak algılanabilir. İkincisi, **odağın kaybolmamasıdır**: odaklı bir öge
ağaçtan çıkarıldığında odak belgenin köküne düşer ve sekme tuşu dolaşıma en baştan başlar.
Bir satırı silen kod, silmeden önce odağın nereye gideceğine karar vermelidir — komşu
satıra ya da listenin kendisine.

Bir üçüncü durum odağı **kapatmayı** gerektirir. Açılan bir iletişim kutusu, arkasındaki
sayfaya sekme tuşuyla geçilmesini engeller; odak kutunun içinde döner. Bu düzenin
kurulması sırasında WCAG 2.1.2 ölçütü bir sınır koyar: kullanıcı klavyeyle girdiği yerden
klavyeyle çıkabilmelidir. Çıkışı olmayan bir odak kuşatması erişilebilirlik hatasıdır.

#### Klavye Olayları

Tuş basımı iki olay üretir: tuşa basıldığında ve tuş bırakıldığında. Basılı tutulan tuş,
işletim sisteminin tekrar aralığına göre yeni basım olayları üretir; olay nesnesindeki
tekrar bayrağı bu olayları ilk basımdan ayırır. Sayaç artıran bir tuş kısayolu, tekrarı
saymak isteyip istemediğine göre bu bayrağı okur.

Olay nesnesi tuşu iki ayrı alanla bildirir ve ikisinin ayrımı kritiktir.

**Anlam alanı** tuşun ürettiği değeri verir: `a`, `Enter`, `ArrowDown`, `Escape`.
Kullanıcının klavye düzenine ve basılı değiştiricilere göre değişir; büyük harf ile küçük
harf farklı değerlerdir.

**Konum alanı** tuşun klavyedeki fiziksel yerini verir. Düzen değişse de aynı kalır.

Seçim kuralı şudur: kısayol bir harfin anlamına dayanıyorsa anlam alanı, tuşun elin
altındaki yerine dayanıyorsa konum alanı okunur. Anlam alanına dayanan kod, farklı
düzenlerde farklı tuşlara düşer; konum alanına dayanan kod ise kullanıcının gördüğü harfle
uyuşmayabilir.

Metin girişini tuş olaylarından türetmek ayrı bir hatadır. Çince, Japonca ve Korece
girişte kullanılan **birleştirme (composition)** düzeneğinde kullanıcı birden çok tuşa
basarak tek bir karakter üretir; ara aşamada alanın içeriği tuşlarla örtüşmez. Aynı biçimde
yapıştırma, sesle giriş ve otomatik doldurma tuş olayı üretmez. Alanın içeriğini öğrenmenin
tek güvenilir yolu, girdi olaylarını dinlemektir; bu Form Olayları dersinin konusudur.

#### Grup İçi Gezinme

Ölçüm tablosunun her satırında bir düğme varsa, sekme tuşu bütün satırları tek tek dolaşır;
yüz satırlık tabloda tablodan çıkmak yüz tuşa bakar. Yerleşik çözüm, grubu tek sekme durağı
yapıp grup içinde ok tuşlarıyla gezinmektir. Düzenin adı **dönen sekme durağı (roving
tabindex)** dır: grupta her an tek bir ögenin `tabindex` değeri sıfırdır, diğerleri eksi
birdir; ok tuşu hem odağı hem de sıfır değerini taşır.

```js
// donen.mjs — grup icinde tek sekme duragi birakan odak yonetimi
const satirlar = ["T-01", "T-02", "T-03", "T-04"];
let etkin = 0;          // gruptaki tek sekme duragi
let odak = "grup disi"; // o an odagi tutan oge

const duraklar = () => satirlar.map((ad, i) => `${ad}:${i === etkin ? 0 : -1}`).join(" ");

function tus(ad) {
  const sonuc = { tus: ad, onlendi: false };
  if (odak === "grup disi") {
    if (ad === "Tab") odak = satirlar[etkin];   // gruba giris: tarayicinin kendi davranisi
    return sonuc;
  }
  switch (ad) {
    case "ArrowDown": etkin = (etkin + 1) % satirlar.length; break;
    case "ArrowUp":   etkin = (etkin - 1 + satirlar.length) % satirlar.length; break;
    case "Home":      etkin = 0; break;
    case "End":       etkin = satirlar.length - 1; break;
    case "Tab":       odak = "grup disi"; return sonuc;   // varsayilan davranis birakilir
    default:          return sonuc;                        // is yapilmadi, iptal istenmez
  }
  odak = satirlar[etkin];
  sonuc.onlendi = true;   // ok tuslarinin kaydirma davranisi iptal edilir
  return sonuc;
}

console.log("baslangic:", odak.padEnd(9), "|", duraklar());
for (const ad of ["Tab", "ArrowDown", "ArrowDown", "End", "Home", "ArrowUp", "x", "Tab"]) {
  const sonuc = tus(ad);
  console.log(
    sonuc.tus.padEnd(9),
    "odak:", odak.padEnd(9),
    "| iptal:", String(sonuc.onlendi).padEnd(5),
    "|", duraklar(),
  );
}
```

```
baslangic: grup disi | T-01:0 T-02:-1 T-03:-1 T-04:-1
Tab       odak: T-01      | iptal: false | T-01:0 T-02:-1 T-03:-1 T-04:-1
ArrowDown odak: T-02      | iptal: true  | T-01:-1 T-02:0 T-03:-1 T-04:-1
ArrowDown odak: T-03      | iptal: true  | T-01:-1 T-02:-1 T-03:0 T-04:-1
End       odak: T-04      | iptal: true  | T-01:-1 T-02:-1 T-03:-1 T-04:0
Home      odak: T-01      | iptal: true  | T-01:0 T-02:-1 T-03:-1 T-04:-1
ArrowUp   odak: T-04      | iptal: true  | T-01:-1 T-02:-1 T-03:-1 T-04:0
x         odak: T-04      | iptal: false | T-01:-1 T-02:-1 T-03:-1 T-04:0
Tab       odak: grup disi | iptal: false | T-01:-1 T-02:-1 T-03:-1 T-04:0
```

Dört davranış çıktıda okunur. Gruba giriş tarayıcının kendi sekme davranışıdır ve iptal
edilmez. Ok tuşları odağı taşır ve yalnızca bu tuşlarda iptal istenir; ok tuşlarının
sayfayı kaydırma davranışı yerine grup içi gezinme konmuştur. Son satırdan önceki `x`
satırı, ele alınmayan tuşlarda iptal istenmediğini gösterir — yazma, kısayollar ve tarayıcı
davranışları etkilenmez. Son sekme tuşu grubu terk eder ve gruptaki durak, en son
kullanılan satırda kalır; kullanıcı geri döndüğünde bıraktığı yerden devam eder.

#### Özet

- Belgede her an en çok bir öge girdiyi alır; odaklanabilirlik doğal odak alma, görünürlük
  ve etkinlik koşullarına bağlıdır.
- Sıfır `tabindex` ögeyi belge sırasındaki yerinde sıraya sokar, eksi bir sıradan çıkarır
  ama programlı odağa açık bırakır, artı değerler bütün sıranın önüne geçerek düzeni bozar.
- Odak olaylarının kabarmayan çifti yalnızca hedefte görülür; devir için kabaran çift ya da
  yakalama kipi kullanılır. Olaylar odağı bırakan ve alan ögeyi birlikte bildirir.
- Odaklı bir öge ağaçtan çıkarılırsa odak kökten yeniden başlar; silme ve kapatma işlemleri
  odağın nereye gideceğine önceden karar vermelidir.
- Tuş olayları anlamı ve fiziksel konumu ayrı alanlarda bildirir; metin girişi tuş
  olaylarından türetilemez.
- Dönen sekme durağı düzeninde grupta tek durak bulunur, ok tuşları odağı ve durağı
  birlikte taşır, ele alınmayan tuşlarda varsayılan davranış korunur.

#### Sonraki Adım

Bu ders metin girişini öğrenmenin tek güvenilir yolunun tuş olayları olmadığını söyledi ve
gerçek yolu sonraya bıraktı. Sayfadaki form yalnızca metin de toplamaz: ölçüm değeri
sayıdır, tarih bir takvim denetiminden gelir, istasyon kodu bir listeden seçilir. Her
denetim değeri farklı anlarda bildirir — kimi her tuşta, kimi alan terk edildiğinde. Bunun
üstünde bir de gönderim vardır: tarayıcı alanları toplar, kısıtları sınar ve isteği kurar.
Sonraki ders bu akışı, hangi olayın hangi anı bildirdiğini ve doğrulamanın nereye
yerleştiğini inceler.

### Form Olayları

Source: https://academia.sh/tr/kurslar/tarayici-platformu/form-olaylari

Değer değişiminden gönderime uzanan akış; girdi ile değişim olaylarının ayrımı, gönderim listesinin kurulması, göndericinin katkısı, kısıt doğrulamasının durumları ve hatanın erişilebilir bildirimi.

Önceki ders, bir alanın içeriğini öğrenmenin güvenilir yolunun tuş olayları olmadığını
söyledi. Sayfadaki form yalnızca metin de toplamaz: ölçüm değeri sayıdır, tarih bir takvim
denetiminden gelir, istasyon kodu bir listeden seçilir. Her denetim değerini farklı
anlarda bildirir.

Bunun üstünde bir de gönderim vardır. Tarayıcı alanları toplar, kısıtları sınar ve isteği
kurar. Bu ders akışı iki parçada ele alır: değer değiştiğinde ne bildirilir, gönderim
istendiğinde ne olur.

#### Değişimi Bildiren Olaylar

Üç olay türü değerin değişimini bildirir ve üçü farklı anları işaretler.

**Girdi öncesi olayı**, değişiklik alana uygulanmadan önce üretilir. Ne olacağını bildirir
ve iptal edilebilir; belirli bir girdiyi tümüyle reddetmek bu olayla mümkündür. Kullanıcının
yazma, yapıştırma, geri alma gibi hangi işlemi yaptığı da bu olayda okunur.

**Girdi olayı**, değişiklik uygulandıktan sonra üretilir. Yazılan her karakterde, her
yapıştırmada, her geri almada çalışır. Değeri sürekli izlemek isteyen kod bunu dinler.
Önceki derste anılan birleştirme düzeneği, sesle giriş ve otomatik doldurma da bu olayı
üretir; tuş olaylarının kaçırdığı durumlar buradan görülür.

**Değişim olayı**, değerin **kesinleştiği** anı bildirir. Metin alanında bu, alanın odağı
kaybetmesidir — gönderimde de olur, çünkü gönderim düğmesine tıklamak alanı önce odaktan çıkarır;
onay kutusunda, seçenek düğmesinde ve açılır listede seçim yapıldığı andır. Yani metin alanlarında
değişim olayı girdi olayından çok daha seyrek, seçim denetimlerinde ise onunla birlikte üretilir.

Seçim kuralı işin niteliğine bağlıdır. Sayfadaki süzgeç alanı yazdıkça sonucu daraltıyorsa
girdi olayı gerekir. Alanın değeri bir kayda yazılacaksa değişim olayı yeterlidir ve daha
azını yapar.

Girdi olayını dinleyen kodun ek bir sorumluluğu vardır: her tuşta çalışan bir işleyici,
her tuşta bir istek üretmemelidir. Yerleşik çözüm **gecikmeli tetikleme**dir; olay
geldiğinde iş hemen yapılmaz, kısa bir süre sonrasına ertelenir ve bu süre içinde yeni
bir olay gelirse erteleme yeniden başlar. Kullanıcı yazmayı bıraktığında tek bir çalışma
kalır. Sürenin ölçülmesi ve iptali Zamanlama ve Çizim Döngüsü dersinin konusudur.

#### Gönderim ve Giriş Listesi

Gönderim, tarayıcının form denetimlerini toplayıp bir istek gövdesi kurmasıdır. Web'in
Temelleri ve HTML kursundaki Form Yapısı dersinde tanımlanan **giriş listesi (entry
list)** bu toplamanın sonucudur. Hangi denetimin listeye girdiği kurallıdır.

```js
// giris-listesi.mjs — gonderimde hangi denetimin listeye girdigi
const denetimler = [
  { ad: "istasyon",   tur: "text",     deger: "Kuzey Yamaç" },
  { ad: "olcum",      tur: "select",   deger: "sicaklik" },
  { ad: "deger",      tur: "number",   deger: "-4.2" },
  { ad: "birim",      tur: "radio",    deger: "C",     isaretli: true },
  { ad: "birim",      tur: "radio",    deger: "F",     isaretli: false },
  { ad: "dogrulandi", tur: "checkbox", deger: "evet",  isaretli: false },
  { ad: "not",        tur: "textarea", deger: "" },
  { ad: "",           tur: "text",     deger: "adsiz alan" },
  { ad: "gizliKod",   tur: "text",     deger: "K-9", devreDisi: true },
  { ad: "eylem",      tur: "submit",   deger: "kaydet" },
  { ad: "eylem",      tur: "submit",   deger: "sil" },
];

// Gonderimi baslatan dugme: yalniz o listeye girer, digerleri girmez.
const gonderici = denetimler.find((d) => d.tur === "submit" && d.deger === "kaydet");

function girisListesi(denetimler, gonderici) {
  const giris = [];
  for (const d of denetimler) {
    if (d.ad === "" || d.devreDisi) continue;
    if ((d.tur === "radio" || d.tur === "checkbox") && d.isaretli !== true) continue;
    if (d.tur === "submit" && d !== gonderici) continue;
    giris.push([d.ad, d.deger]);
  }
  return giris;
}

const giris = girisListesi(denetimler, gonderici);
for (const [ad, deger] of giris) console.log(`  ${ad.padEnd(11)} = ${JSON.stringify(deger)}`);
console.log("govde:", new URLSearchParams(giris).toString());
```

```
  istasyon    = "Kuzey Yamaç"
  olcum       = "sicaklik"
  deger       = "-4.2"
  birim       = "C"
  not         = ""
  eylem       = "kaydet"
govde: istasyon=Kuzey+Yama%C3%A7&olcum=sicaklik&deger=-4.2&birim=C&not=&eylem=kaydet
```

Dört kural çıktıdan okunur. **Adı olmayan denetim listeye girmez**; ad, alanın sunucuya
görünen kimliğidir. **Devre dışı denetim listeye girmez**; bu, önceki derste anılan
"devre dışı bırakmanın görünmez kılması" kuralının veri tarafındaki karşılığıdır.
**İşaretlenmemiş onay kutusu ve seçilmemiş seçenek düğmesi listeye girmez**; alıcı taraf
bu alanların yokluğunu "hayır" olarak yorumlamak zorundadır. **Boş metin alanı listeye
girer**, çünkü boş bir değer de bir değerdir.

Gönderici düğme ayrı bir katkı yapar. Formda birden çok gönderim düğmesi varsa listeye
yalnızca tıklanan düğmenin adı ve değeri girer; alıcı taraf hangi eylemin istendiğini
buradan öğrenir. Gönderim olayı, gönderici düğmeyi olay nesnesi üzerinden bildirir.

Gönderim olayı iptal edilebilir. Varsayılan Davranış ve İptal dersindeki kural burada
işler: gönderimi iptal eden kod, veriyi kendisi göndermek zorundadır. Giriş listesinin
program tarafından da kurulabilen bir karşılığı vardır ve isteğe olduğu gibi verilebilir;
böylece sunucu tarafı iki yolda da aynı gövdeyi görür.

#### Kısıt Doğrulaması

Gönderim olayı üretilmeden önce tarayıcı **kısıt doğrulamasını (constraint validation)**
çalıştırır. Kısıtlar işaretlemede bildirilir; sonuçları bir durum kümesi olarak okunur.

```js
// gecerlilik.mjs — kisit ozniteliklerinden gecerlilik durumunun turetilmesi
const KISIT = { zorunlu: true, enAz: -50, enCok: 60, adim: 0.1, taban: 0 };

// Adim denetimi ikilik kesir hatasindan kacinmak icin tam sayiya olceklenir.
const olcek = 1000;
const adimUyumsuz = (sayi, k) =>
  Math.round((sayi - k.taban) * olcek) % Math.round(k.adim * olcek) !== 0;

function gecerlilik(ham, k = KISIT, devreDisi = false) {
  const durum = {
    degerEksik: false, turUyumsuz: false, aralikAlti: false,
    aralikUstu: false, adimUyumsuz: false,
  };
  if (devreDisi) return { dogrulanir: false, durum, gecerli: true };

  if (k.zorunlu && ham === "") durum.degerEksik = true;
  else if (ham !== "") {
    const sayi = Number(ham);
    if (Number.isNaN(sayi)) durum.turUyumsuz = true;
    else {
      if (sayi < k.enAz) durum.aralikAlti = true;
      if (sayi > k.enCok) durum.aralikUstu = true;
      if (adimUyumsuz(sayi, k)) durum.adimUyumsuz = true;
    }
  }
  const gecerli = Object.values(durum).every((v) => v === false);
  return { dogrulanir: true, durum, gecerli };
}

const ornekler = [
  ["", false], ["-4.2", false], ["-80", false], ["61.5", false],
  ["12.34", false], ["cok soguk", false], ["999", true],
];

for (const [ham, devreDisi] of ornekler) {
  const { dogrulanir, durum, gecerli } = gecerlilik(ham, KISIT, devreDisi);
  const bozuk = Object.entries(durum).filter(([, v]) => v).map(([ad]) => ad);
  console.log(
    JSON.stringify(ham).padEnd(12),
    "dogrulanir:", String(dogrulanir).padEnd(5),
    "gecerli:", String(gecerli).padEnd(5),
    bozuk.join(", "),
  );
}
```

```
""           dogrulanir: true  gecerli: false degerEksik
"-4.2"       dogrulanir: true  gecerli: true  
"-80"        dogrulanir: true  gecerli: false aralikAlti
"61.5"       dogrulanir: true  gecerli: false aralikUstu
"12.34"      dogrulanir: true  gecerli: false adimUyumsuz
"cok soguk"  dogrulanir: true  gecerli: false turUyumsuz
"999"        dogrulanir: false gecerli: true  
```

Model üç şeyi gösterir. Birincisi, geçerlilik tek bir doğru–yanlış değil, **ayrı ayrı
okunabilen durumlardan** oluşur; hata iletisi hangi kısıtın bozulduğuna göre yazılabilir.
İkincisi, adım kısıtı ondalık değerlerde doğrudan bölme ile sınanamaz; Bilgisayarlar Nasıl
Çalışır kursundaki ikilik kesir sorunu burada belirir ve sınama tam sayıya ölçeklenerek
yapılır. Üçüncüsü, devre dışı denetim **hiç doğrulanmaz**; son satırdaki aralık dışı değer
geçerli sayılmıştır, çünkü o denetim doğrulamaya katılmaz.

Tarayıcının kendi sınaması, alanın ham değerini de düzeltebilir. Sayı bekleyen bir alana
sayı olmayan bir dizgi yazıldığında değer boşaltılabilir; o durumda bildirilen durum "tür
uyumsuz" değil "değer eksik" olur. Bu nedenle hata iletisi, kullanıcının gördüğü değere
değil bildirilen duruma bakılarak seçilir.

Program tarafında üç işlem tanımlıdır. Biri geçerliliği **sınar** ve sonucu döndürür;
ikincisi sınar ve geçersizse tarayıcının kendi iletisini **gösterir**; üçüncüsü alana özel
bir hata iletisi **yazar**. Üçüncüsü uygulama kurallarını taşımak için vardır: işaretlemede
bildirilemeyen bir kural — ölçüm tarihinin gelecekte olmaması gibi — bu yolla yerleşik
düzeneğe bağlanır. Boş bir ileti yazmak alanı yeniden geçerli kılar.

Formun tamamında doğrulamayı kapatan bir bildirim de vardır. Bu bildirim tarayıcının
kendi iletilerini kaldırır; kısıtlar okunmaya devam eder. Kendi hata sunumunu kuran
sayfalar bu yolu izler. Hiçbir durumda istemci tarafı doğrulama sunucu tarafı doğrulamanın
yerini tutmaz: istek, sayfa hiç kullanılmadan da kurulabilir.

#### Hatayı Bildirmek

Geçersiz her alan için bir olay üretilir ve bu olay kabarmaz; formun tamamındaki hataları
tek yerde toplamak isteyen kod yakalama kipini kullanır. Olay Devri dersindeki kural bu
noktada ikinci kez karşımıza çıkar.

Hatanın sunumu üç parçadan oluşur. Alanın yanında **ilgili ileti** durur ve alana ek
açıklama bağıyla iliştirilir; alan geçersizlik bildirimiyle işaretlenir. Formun başında bir
**hata özeti** bulunur ve her madde ilgili alana bağlantı verir. Gönderim denemesinden
sonra **odak ilk geçersiz alana** ya da özetin kendisine taşınır; Odak ve Klavye Etkileşimi
dersindeki programlı odak kuralı burada işler.

Hata iletilerinin yazılma anı da bir tasarım kararıdır. Kullanıcı alanı doldururken her
tuşta hata göstermek, henüz bitmemiş bir girdiyi hatalı ilan eder. Yaygın düzen şudur: ilk
ileti alan terk edildiğinde ya da gönderim denendiğinde yazılır; alan bir kez hatalı
bulunduktan sonra girdi olayında güncellenir, böylece düzeltme anında ileti kaybolur.

Formun sıfırlanması ayrı bir olay üretir ve iptal edilebilir. Sıfırlama, alanları
işaretlemede bildirilen başlangıç değerlerine döndürür — DOM API'si dersindeki öznitelik
ile özellik ayrımının doğrudan sonucudur. Kendi tuttuğu durumu bu olayda temizlemeyen
kod, ekranda boşalmış ama bellekte dolu bir formla kalır.

#### Özet

- Girdi öncesi olayı değişiklik uygulanmadan önce üretilir ve iptal edilebilir; girdi olayı
  her değişiklikte, değişim olayı değerin kesinleştiği anda üretilir.
- Girdi olayına bağlanan iş gecikmeli tetiklemeyle seyreltilir; her tuş bir istek üretmez.
- Giriş listesine adı olan, devre dışı olmayan ve seçim denetimlerinde işaretli olan
  denetimler girer; birden çok gönderim düğmesinden yalnızca tıklanan katkı verir.
- Kısıt doğrulaması tek bir doğru–yanlış değil ayrı ayrı okunabilen durumlar üretir; devre
  dışı denetimler doğrulamaya hiç katılmaz.
- Özel hata iletisi yazan işlem, işaretlemede bildirilemeyen uygulama kurallarını yerleşik
  doğrulamaya bağlar; istemci tarafı doğrulama sunucu tarafının yerini tutmaz.
- Geçersizlik olayı kabarmaz; hata sunumu ileti, özet ve odak taşıma olmak üzere üç parça
  hâlinde kurulur.

#### Sonraki Adım

Bu ders formun bir gönderimle bittiğini varsaydı. Oysa çoğu form yarım kalır: kullanıcı
ölçüm değerini girer, sekmeyi kapatır, ertesi gün geri döner. Sayfa yeniden yüklendiğinde
bellekte tutulan her şey silinmiştir; süzgeç alanına yazılan ölçüt de, yarım kalan form da.
Bunları saklamak, tarayıcının sayfa ömrünü aşan bir yere yazmayı gerektirir. Sonraki ders
bu yerleri — sekme ömrüyle sınırlı olanı, kaynak ömrü boyunca kalanı ve yapılandırılmış
veriyi tutanı — ayrımlarıyla birlikte ele alır.

## Tarayıcı API'leri

Depolama katmanları, geçmiş ve adres yönetimi, gözlemciler, kare tabanlı güncelleme, biçimleme ve ana iş parçacığını boşaltan çalışan iş parçacıkları.

### Depolama API'leri

Source: https://academia.sh/tr/kurslar/tarayici-platformu/depolama-apileri

Sayfa ömrünü aşan veriyi tutan katmanlar; oturum ve kaynak ömrü ayrımı, dizgi deposunun tip ve serileştirme kayıpları, kota ve tahliye, sekmeler arası bildirim, şema göçü ve depolanmaması gereken veri.

Önceki ders formun bir gönderimle bittiğini varsaydı. Çoğu form yarım kalır: kullanıcı
ölçüm değerini girer, sekmeyi kapatır, ertesi gün geri döner. Sayfa yeniden yüklendiğinde
bellekteki her şey silinmiştir — süzgeç alanına yazılan ölçüt de, yarım kalan form da.

Bunları saklamak, tarayıcının sayfa ömrünü aşan bir yere yazmayı gerektirir. Böyle birden
çok yer vardır ve aralarındaki fark boyut değil **ömür, erişim biçimi ve sunucuya
görünürlüktür**.

#### Katmanlar

**Oturum depolama (session storage)** sekme ömrüyle sınırlıdır. Aynı adres iki sekmede
açıldığında iki ayrı depo görülür; sekme kapandığında içerik silinir. Yarım kalan bir form
taslağı, çok adımlı bir akışın ara durumu buraya yazılır.

**Yerel depolama (local storage)** kaynak ömrü boyunca kalır. Sekme kapansa da, tarayıcı
kapansa da içerik durur; aynı kaynağın bütün sekmeleri aynı depoyu paylaşır. Kullanıcı
tercihleri, süzgeç ölçütü, son görüntülenen ölçüm buraya yazılır.

**Yapılandırılmış depolama (IndexedDB)** anahtar–değer ikilisinden fazlasını tutar:
dizinlenmiş kayıtlar, işlemler, aralık sorguları. Erişimi eşzamansızdır. Çevrimdışı
çalışacak bir ölçüm günlüğü bu katmanda durur.

**Çerez (cookie)** diğerlerinden farklı bir amaca hizmet eder: İnternet Nasıl Çalışır
kursunda tanımlandığı gibi her istekle sunucuya gider. Bu, oturum kimliği için istenen
davranıştır; arayüz durumu için istenmeyen bir yüktür. Yalnız istemcide kalacak veri
çerezde tutulmaz.

Dördünün ortak sınırı **kaynaktır (origin)**. Şema, ana bilgisayar ve bağlantı noktasının
oluşturduğu üçlü ayrıysa depo da ayrıdır; aynı sunucuda barınan iki farklı kaynak
birbirinin verisini göremez. Çerezin sınırı ayrıca alan adı ve yol bileşenleriyle
daraltılabilir.

#### Dizgi Deposunun Davranışı

İlk iki katman yalnızca dizgi tutar. Bu tek cümle üç ayrı sonuç doğurur.

```js
// depo.mjs — dizgi tabanli anahtar-deger deposunun uc davranisi
class Depo {
  constructor(kota) { this.kota = kota; this.kayit = new Map(); }
  get doluluk() {
    let t = 0;
    for (const [a, d] of this.kayit) t += a.length + d.length;
    return t;
  }
  yaz(anahtar, deger) {
    const metin = String(deger);              // her deger dizgiye cevrilir
    const eski = this.kayit.get(anahtar)?.length ?? 0;
    if (this.doluluk - eski + anahtar.length + metin.length > this.kota)
      throw new Error("KotaAsildi");
    this.kayit.set(anahtar, metin);
  }
  oku(anahtar) { return this.kayit.get(anahtar) ?? null; }
}

const depo = new Depo(100);  // kota, kod birimi sayisiyla olculuyor

// 1. Tip kaybi: yazilan sayi, okunan dizgidir.
depo.yaz("esik", 12.5);
console.log("okunan:", JSON.stringify(depo.oku("esik")), "tip:", typeof depo.oku("esik"));
console.log("olmayan anahtar:", depo.oku("yok"));

// 2. Serilestirme kaybi: JSON turu koruyan bir bicim degildir.
const olcum = { kod: "T-01", an: new Date("2024-02-11T06:00:00Z"), not: undefined,
                etiketler: new Set(["gece"]) };
depo.yaz("olcum", JSON.stringify(olcum));
const geri = JSON.parse(depo.oku("olcum"));
console.log("yazilan:", { an: olcum.an.constructor.name, not: olcum.not, etiketler: olcum.etiketler });
console.log("okunan :", geri);

// 3. Kota: yazma islemi hata atar, sessizce basarisiz olmaz.
try {
  depo.yaz("gunluk", "x".repeat(200));
} catch (hata) {
  console.log("hata:", hata.message, "| doluluk:", depo.doluluk, "/", depo.kota);
}
console.log("kota asildiktan sonra 'esik':", JSON.stringify(depo.oku("esik")));
```

```
okunan: "12.5" tip: string
olmayan anahtar: null
yazilan: { an: 'Date', not: undefined, etiketler: Set(1) { 'gece' } }
okunan : { kod: 'T-01', an: '2024-02-11T06:00:00.000Z', etiketler: {} }
hata: KotaAsildi | doluluk: 74 / 100
kota asildiktan sonra 'esik': "12.5"
```

**Tip kaybı.** Yazılan sayı okunurken dizgidir. Karşılaştırmayı dönüştürmeden yapan kod
sessizce yanlış çalışır. Bulunmayan anahtar ise boş dizgi değil boş değer döndürür; ikisi
ayırt edilmelidir, çünkü kullanıcı bir alanı bilerek boş bırakmış olabilir.

**Serileştirme (serialization) kaybı.** JSON, tipleri koruyan bir biçim değildir. Tarih nesnesi dizgiye
döner, tanımsız alan tümüyle düşer, küme boş nesneye dönüşür. Okuma tarafı yalnızca
`JSON.parse` çağırmakla yetinemez; kaydı beklenen tiplere geri çeviren bir katman gerekir.
Tarayıcının iş parçacıkları arasında veri taşırken JSON yerine ayrı bir kopyalama düzeneği
kullanmasının nedeni de budur; o düzenek Web İşçileri dersinde ele alınır.

**Kota.** Depo sınırsız değildir ve sınır aşıldığında yazma **hata atar**. Kotanın büyüklüğü
gerçekleştirime bağlıdır; yazılan kodun buna bir sayı varsayarak değil, hatayı yakalayarak
uyum sağlaması gerekir. Çıktının son satırı ikinci bir kuralı gösterir: başarısız yazma
önceki kaydı bozmaz. Yazma yolu bu nedenle her zaman bir yakalama bloğu içinde durur ve
başarısızlıkta kullanıcıya görünür bir davranış üretir.

Kotayı aşmadan da veri kaybolabilir. Tarayıcı, yer daraldığında ya da veri uzun süre
kullanılmadığında bir kaynağın depolamasını **tahliye** edebilir. Kullanıcı da depolamayı
temizleyebilir. Bu nedenle depolama bir önbellek gibi ele alınır: içeriği olmadan da
çalışan bir sayfa doğru tasarlanmıştır.

#### Sekmeler Arası Bildirim

Yerel depolama aynı kaynağın bütün sekmelerince paylaşıldığı için bir sekmedeki yazma
diğerlerini ilgilendirir. Tarayıcı bunun için bir olay üretir; olay eski değeri, yeni
değeri ve anahtarı taşır. Böylece bir sekmede değiştirilen süzgeç ölçütü diğer sekmede de
uygulanabilir.

Bu olayın iki özelliği şaşırtıcıdır. Birincisi, **yazan sekmede üretilmez**; yalnızca diğer
sekmeler bildirim alır. Yazan sekmenin kendi arayüzünü güncellemesi kendi sorumluluğudur.
İkincisi, oturum depolamada bu bildirim anlamsızdır, çünkü depo zaten sekmeye özeldir.

#### Eşzamanlılık Maliyeti

İlk iki katmanın erişimi **eşzamanlıdır**: okuma ve yazma, çağrıldığı anda tamamlanır ve
ana iş parçacığı bu sırada başka iş yapamaz. Küçük değerlerde bu görünmez. Büyük bir
kaydı her tuş vuruşunda yazan kod ise kullanıcı arayüzünü bekletir; Olay Modeli dersinde
anılan kuyruk gecikmesi burada da işler.

Üç kural bu maliyeti sınırlar. Yazma seyrekleştirilir — Form Olayları dersindeki gecikmeli
tetikleme burada da geçerlidir. Büyük veri yapılandırılmış depolamaya taşınır; onun
eşzamansız arayüzü ana iş parçacığını bekletmez. Okuma, sayfa açılışında bir kez yapılıp
bellekte tutulur; her erişimde yeniden okunmaz.

#### Şema Değişimi

Depolanan veri, onu yazan koddan daha uzun yaşar. Kullanıcının tarayıcısında altı ay
önceki biçimle yazılmış bir kayıt durabilir. Bu nedenle her kayıt bir **sürüm** alanı
taşır ve okuma yolu, eski sürümü güncel biçime taşıyan bir zincir içerir.

```js
// goc.mjs — depodaki kaydin surumlenmesi ve ileri goc
const GUNCEL = 3;
const GOC = {
  1: (k) => ({ ...k, birim: "C" }),                                   // 1 -> 2: birim eklendi
  2: ({ deger, ...k }) => ({ ...k, sicaklik: Number(deger) }),        // 2 -> 3: alan adi ve tipi
};

function oku(ham) {
  let kayit;
  try { kayit = JSON.parse(ham); }
  catch { return { sonuc: "cozumlenemedi", kayit: null }; }

  const surum = kayit.surum ?? 1;
  if (surum > GUNCEL) return { sonuc: "gelecek surum, yok sayildi", kayit: null };

  let s = surum;
  while (s < GUNCEL) { kayit = { ...GOC[s](kayit), surum: s + 1 }; s += 1; }
  return { sonuc: `surum ${surum} -> ${GUNCEL}`, kayit };
}

const ornekler = [
  '{"kod":"T-01","deger":"-4.2"}',                                  // surum alani yok: 1 sayilir
  '{"surum":2,"kod":"T-02","deger":"3.5","birim":"C"}',
  '{"surum":3,"kod":"T-03","sicaklik":11.8,"birim":"C"}',
  '{"surum":9,"kod":"T-04"}',
  '{kod:"T-05"}',
];

for (const ham of ornekler) {
  const { sonuc, kayit } = oku(ham);
  console.log(sonuc.padEnd(26), JSON.stringify(kayit));
}
```

```
surum 1 -> 3               {"kod":"T-01","birim":"C","surum":3,"sicaklik":-4.2}
surum 2 -> 3               {"surum":3,"kod":"T-02","birim":"C","sicaklik":3.5}
surum 3 -> 3               {"surum":3,"kod":"T-03","sicaklik":11.8,"birim":"C"}
gelecek surum, yok sayildi null
cozumlenemedi              null
```

Zincir dört durumu birden karşılar. Sürüm alanı olmayan en eski kayıt birinci sürüm
sayılır ve iki adımda güncel biçime taşınır. Ara sürüm kalan adımı atlar. Güncel kayıt
dokunulmadan geçer.

Son iki satır savunma tarafıdır. **Gelecekten gelen** bir kayıt — kullanıcının başka bir
cihazdan eşitlediği ya da daha yeni bir sürümle yazılmış olan — geriye taşınamaz; okunmaz
ve yok sayılır. **Çözümlenemeyen** içerik de aynı biçimde ele alınır. Depodan okunan her
şey dış girdidir: bozuk olabilir, elle değiştirilmiş olabilir, hiç yazılmamış olabilir.
Çözümleme her zaman bir yakalama bloğu içinde yapılır.

#### Ne Saklanmaz

Depolama, kullanıcının cihazında ve kullanıcının denetiminde duran bir alandır. Buradan üç
kural çıkar.

Sayfada çalışan her kod aynı depoyu okuyabilir; bu nedenle **kimlik belirteci, parola ve
kişisel veri** oraya yazılmaz. Bir betik enjeksiyonu, depodaki her şeye erişir.

Depo **kaydın kaynağı değildir**. Sunucudaki veriye göre ikincildir; çakışma durumunda
hangisinin kazanacağı açıkça kararlaştırılır.

Kullanıcı depoyu her an temizleyebilir ve tarayıcı tahliye edebilir. Bu nedenle depolanan
veri **yeniden üretilebilir** olmalıdır; kaybı bir işlevi kalıcı olarak bozmamalıdır.

#### Özet

- Oturum depolama sekme ömrüyle, yerel depolama kaynak ömrüyle sınırlıdır; yapılandırılmış
  depolama dizinli kayıtları eşzamansız arayüzle tutar, çerez ise her istekle sunucuya gider.
- Dizgi depoları yazılan değeri dizgiye çevirir ve JSON tipleri korumaz; okuma tarafı kaydı
  beklenen tiplere geri çeviren bir katman gerektirir.
- Kota aşımı hata atar ve önceki kaydı bozmaz; kotanın büyüklüğü varsayılmaz, hata yakalanır.
- Yerel depolamadaki değişiklik diğer sekmelere olayla bildirilir; yazan sekme bu olayı
  görmez ve kendi arayüzünü kendisi günceller.
- Eşzamanlı erişim ana iş parçacığını bekletir; yazma seyreltilir, büyük veri
  yapılandırılmış depolamaya taşınır, okuma bir kez yapılıp bellekte tutulur.
- Kayıtlar sürüm alanı taşır ve okuma yolunda göç zincirinden geçer; gelecek sürümlü ya da
  çözümlenemeyen içerik yok sayılır.

#### Sonraki Adım

Bu ders durumu sakladı ama durumun **adresle** ilişkisine dokunmadı. Süzgeç ölçütü depoda
tutulduğunda sayfa yeniden açıldığında geri gelir; ancak o hâlin bir adresi yoktur.
Kullanıcı süzülmüş listeyi paylaşamaz, yer imine ekleyemez, geri düğmesiyle önceki süzgece
dönemez. Bunlar depolamanın değil gezinmenin sorunlarıdır ve tarayıcının adres çubuğu ile
geçmiş yığıtı üzerinden çözülür. Sonraki ders adresin parçalarını, geçmiş yığıtının nasıl
değiştirildiğini ve istemci tarafı gezinmenin hangi noktada sunucuyla anlaşmak zorunda
olduğunu inceler.

### Geçmiş ve Konum

Source: https://academia.sh/tr/kurslar/tarayici-platformu/gecmis-ve-konum

Sayfa durumunun adrese yazılması ve geçmiş yığıtının programla yönetilmesi; adresin parçaları, hangi durumun yola sorguya ya da parçaya ait olduğu, ekleme ile değiştirme ayrımı, gezinme olayı ve sunucu tarafının payı.

Önceki ders durumu sakladı ama durumun adresle ilişkisine dokunmadı. Süzgeç ölçütü depoda
tutulduğunda sayfa yeniden açıldığında geri gelir; ancak o hâlin bir adresi yoktur.
Kullanıcı süzülmüş listeyi paylaşamaz, yer imine ekleyemez, geri düğmesiyle önceki süzgece
dönemez.

Bunlar depolamanın değil **gezinmenin** sorunlarıdır. Tarayıcının adres çubuğu ve geçmiş
yığıtı, sayfa yeniden yüklenmeden de değiştirilebilir. Bu ders adresin parçalarını,
yığıtın nasıl yönetildiğini ve istemci tarafı gezinmenin sunucudan ne istediğini kurar.

#### Adresin Parçaları

Web'in Temelleri ve HTML kursundaki Bağlantılar dersinde adres bir dizgi olarak
tanıtılmıştı. Program tarafında adres, parçalarına ayrılmış bir nesnedir ve her parça ayrı
ayrı okunup yazılabilir.

```js
// adres.mjs — adresin parcalari ve sorgu dizgisinin duzenlenmesi
const adres = new URL("https://ornek.gecerli/istasyon/kuzey-yamac/gunluk?olcum=sicaklik&esik=-5&sayfa=3#ozet");

for (const alan of ["protocol", "host", "pathname", "search", "hash"])
  console.log(alan.padEnd(9), JSON.stringify(adres[alan]));

// Suzgec degistiginde yalniz ilgili parametreler yazilir, digerleri korunur.
const yeni = new URL(adres);
yeni.searchParams.set("olcum", "bagil nem");
yeni.searchParams.delete("sayfa");            // suzgec degisince sayfalama basa doner
console.log("yeni      ", yeni.pathname + yeni.search + yeni.hash);

// Ayni ada birden cok deger baglanabilir; okuma iki ayri islemle yapilir.
yeni.searchParams.append("etiket", "gece");
yeni.searchParams.append("etiket", "kar");
console.log("etiket tek", JSON.stringify(yeni.searchParams.get("etiket")));
console.log("etiket cok", JSON.stringify(yeni.searchParams.getAll("etiket")));

// Goreli adres, taban adrese gore cozumlenir.
console.log("goreli    ", new URL("../ozet?olcum=sicaklik", adres).href);
```

```
protocol  "https:"
host      "ornek.gecerli"
pathname  "/istasyon/kuzey-yamac/gunluk"
search    "?olcum=sicaklik&esik=-5&sayfa=3"
hash      "#ozet"
yeni       /istasyon/kuzey-yamac/gunluk?olcum=bagil+nem&esik=-5#ozet
etiket tek "gece"
etiket cok ["gece","kar"]
goreli     https://ornek.gecerli/istasyon/ozet?olcum=sicaklik
```

Sorgu dizgisini elle birleştirmek yerine sağlanan arayüzle düzenlemenin üç kazancı çıktıda
görünür. Kaçırma kendiliğinden yapılır: boşluk içeren değer kodlanmıştır. İlgisiz
parametreler korunur; süzgeç değişirken eşik değeri yerinde kalmıştır. Aynı ada bağlı
birden çok değer desteklenir ve tek değer okuma ile tüm değerleri okuma ayrı işlemlerdir.

Sayfalama parametresinin silinmesi kod değil karar örneğidir: süzgeç değiştiğinde eski
sayfa numarası anlamını yitirir. Adres durumu taşıdığı için, durumlar arası bu tür
bağımlılıklar da adres üzerinde işletilir.

#### Hangi Durum Adrese Yazılır

Adresin her parçası farklı bir soruya karşılık gelir.

**Yol (path)** kaynağın kimliğidir: hangi istasyonun günlüğü görüntüleniyor. Kaynak
değişiyorsa yol değişir.

**Sorgu (query string)** aynı kaynağın hangi görünümünün istendiğini söyler: süzgeç,
sıralama, sayfa numarası. Ölçüm listesinin süzülmüş hâli buraya yazılır ve böylece
paylaşılabilir olur.

**Parça (fragment)** belge içindeki konumu gösterir ve sunucuya hiç gönderilmez. Sayfa
içi bağlantılar ve sunucunun bilmesi gerekmeyen geçici görünüm ayarları buraya uygundur.

Adrese **yazılmaması gereken** durum da vardır: bir düğmenin üzerine gelinmesi, bir
animasyonun ilerlemesi, açık bir ipucu balonu. Ölçüt şudur: kullanıcı bu adresi yeni bir
sekmede açtığında aynı ekranı görmeyi bekliyor mu. Yanıt hayırsa durum adrese girmez.

Bir de gizlilik ölçütü vardır. Adres kütüklere yazılır, yönlendirenler üzerinden dış
sunuculara gidebilir ve kullanıcı tarafından paylaşılır; kişisel veri ve belirteç adrese
konmaz.

#### Geçmiş Yığıtı

Tarayıcının geçmişi bir yığıt değil, üzerinde ileri geri hareket edilen bir kayıt dizisidir.
Program bu diziyi üç işlemle etkiler.

```js
// gecmis.mjs — gecmis yiginin uc islemi ve hangisinin olay urettigi
class Gecmis {
  constructor(ilk) { this.girisler = [ilk]; this.dizin = 0; }
  get simdi() { return this.girisler[this.dizin]; }
  ekle(giris) {                       // ileri kayitlar atilir
    this.girisler.length = this.dizin + 1;
    this.girisler.push(giris);
    this.dizin += 1;
    return null;                      // olay uretilmez
  }
  degistir(giris) {
    this.girisler[this.dizin] = giris;
    return null;                      // olay uretilmez
  }
  git(adim) {
    const hedef = this.dizin + adim;
    if (hedef < 0 || hedef >= this.girisler.length) return "sinir disi, is yapilmadi";
    this.dizin = hedef;
    return this.simdi.durum;          // olay uretilir, durum nesnesi tasinir
  }
}

const g = new Gecmis({ ad: "liste", durum: { olcum: "hepsi" } });
const yaz = (islem, olay) =>
  console.log(
    islem.padEnd(16),
    "dizin:", g.dizin,
    "| yigin:", g.girisler.map((x) => x.ad).join(" "),
    "| olay:", olay === null ? "yok" : JSON.stringify(olay),
  );

yaz("baslangic", null);
yaz("ekle(sicaklik)", g.ekle({ ad: "sicaklik", durum: { olcum: "sicaklik" } }));
yaz("ekle(nem)", g.ekle({ ad: "nem", durum: { olcum: "nem" } }));
yaz("degistir(nem*)", g.degistir({ ad: "nem*", durum: { olcum: "nem", sayfa: 2 } }));
yaz("git(-1)", g.git(-1));
yaz("git(-1)", g.git(-1));
yaz("git(-1)", g.git(-1));
yaz("ekle(ruzgar)", g.ekle({ ad: "ruzgar", durum: { olcum: "ruzgar" } }));
yaz("git(+1)", g.git(1));
```

```
baslangic        dizin: 0 | yigin: liste | olay: yok
ekle(sicaklik)   dizin: 1 | yigin: liste sicaklik | olay: yok
ekle(nem)        dizin: 2 | yigin: liste sicaklik nem | olay: yok
degistir(nem*)   dizin: 2 | yigin: liste sicaklik nem* | olay: yok
git(-1)          dizin: 1 | yigin: liste sicaklik nem* | olay: {"olcum":"sicaklik"}
git(-1)          dizin: 0 | yigin: liste sicaklik nem* | olay: {"olcum":"hepsi"}
git(-1)          dizin: 0 | yigin: liste sicaklik nem* | olay: "sinir disi, is yapilmadi"
ekle(ruzgar)     dizin: 1 | yigin: liste ruzgar | olay: yok
git(+1)          dizin: 1 | yigin: liste ruzgar | olay: "sinir disi, is yapilmadi"
```

Dört kural bu çıktıda okunur.

**Ekleme yeni bir kayıt açar ve ileri kayıtları atar.** Son satırlarda geri gidildikten
sonra yapılan ekleme, ileri yöndeki iki kaydı silmiştir; ileri düğmesi artık çalışmaz. Bu,
tarayıcının kendi gezinmesinde de aynıdır.

**Değiştirme kayıt sayısını artırmaz.** Süzgeç alanına yazılan her karakter için ekleme
yapan bir sayfa, kullanıcının geri düğmesini kullanılamaz hâle getirir: her basış bir harf
geri alır. Kullanıcının geri dönmek isteyeceği durumlar eklenir; ara durumlar değiştirilir.

**Olay yalnızca dolaşımda üretilir.** Ekleme ve değiştirme olay üretmez; bunları yapan kod
arayüzü kendisi günceller. Bu, önceki derste yerel depolama olayının yazan sekmede
üretilmemesiyle aynı tasarımdır.

**Her kaydın kendi durum nesnesi vardır.** Olay bu nesneyi taşır; sayfa geri gidildiğinde hangi
görünümün kurulacağı buradan okunur. Durum nesnesi serileştirilerek saklandığı için Depolama
API'leri dersindeki sınır burada da geçerlidir: işlev, düğüm ve devingen nesne konamaz; boyutu
gerçekleştirime bağlı bir sınırla kısıtlıdır. Sağlam yol, durum nesnesine yalnızca adresten
türetilemeyen küçük bilgileri koymaktır — kaydırma konumu, açık panelin kimliği gibi.

Modelin bir noktası tarayıcıdan ayrılır: burada sınırın dışına çıkan hareket iş yapmadan
döner. Tarayıcıda geçmiş sekmeye aittir ve sayfadan önceki kayıtları da içerir; ilk kaydın
gerisine gitmek siteden çıkar. Kod bu nedenle "geri gidilecek kayıt var mı" varsayımı
kuramaz.

#### Sunucu Tarafının Payı

İstemci tarafı gezinme adresi sayfa yeniden yüklenmeden değiştirir. Bunun bir koşulu
vardır ve unutulduğunda hata yalnızca ikinci ziyarette görünür: **kullanıcı o adresi
doğrudan açtığında sunucu geçerli bir yanıt vermelidir.**

Adres çubuğunda süzülmüş listenin adresi görünüyorsa, kullanıcı o adresi kopyalayıp yeni
bir sekmede açabilir, yer imine ekleyebilir, arama sonucundan gelebilir. Bu isteklerde
tarayıcı yeni bir belge ister. Sunucu o yolu tanımıyorsa yanıt bulunamadı olur. İnternet
Nasıl Çalışır kursunda tanımlanan durum kodları burada doğrudan kullanıcıya yansır.

İki çözüm sınıfı vardır. Sunucu her yol için gerçek bir belge üretir; ya da tanımlı yol
örüntülerini tek bir belgeye eşler ve görünümü istemci kurar. İkinci yolda dikkat edilecek
nokta, gerçekten bulunmayan adreslerin yine de bulunamadı yanıtı almasıdır; her adrese
başarılı yanıt veren bir sunucu, arama motorlarına ve bağlantı denetleyicilerine yanlış
bilgi verir.

Konum nesnesine yazmak ayrı bir işlemdir ve **her zaman gerçek gezinme başlatır**. Bu
gezinmenin geçmişe kayıt ekleyen ve o anki kaydın yerine geçen iki biçimi vardır;
ikincisi, kullanıcının geri döndüğünde yeniden yönlendirileceği ara adresler için
kullanılır.

#### Gezinme Sırasında İki Sorumluluk

Sayfa yeniden yüklenmeden yapılan gezinme, tarayıcının kendiliğinden yaptığı iki işi de
devralır.

Birincisi **kaydırma konumudur**. Gerçek gezinmede tarayıcı, geri dönülen sayfanın
kaydırma konumunu geri yükler. Belge yerinde kalıp yalnızca içeriği değişen bir sayfada
bu davranış yanlış zamanda çalışabilir: yeni içerik henüz yerleşmemişken eski konuma
gidilir. Tarayıcı bu nedenle geri yüklemeyi elle üstlenmeye izin veren bir ayar sunar;
sayfa konumu kendi durum nesnesinde saklar ve içerik yerleştikten sonra uygular.

İkincisi **iptal edilmeyen isteklerdir**. Yeni görünüm için veri istendiğinde kullanıcı
beklemeden başka bir bağlantıya geçebilir. Eski isteğin yanıtı sonra gelirse, ekranda
duran görünümün üstüne eski veri yazılır. Eşzamansız JavaScript ve Çalışma Zamanı
kursundaki İptal ve Zaman Aşımı dersinde tanımlanan iptal işareti bu yarışın çözümüdür:
her gezinme kendi işaretini açar ve sonraki gezinme öncekini iptal eder. İptal edilmemiş
isteklerin yanıtı, geldiğinde hangi gezinmeye ait olduğu sınanarak yok sayılır.

#### Özet

- Adres parçalara ayrılmış bir nesnedir; sorgu dizgisi sağlanan arayüzle düzenlendiğinde
  kaçırma kendiliğinden yapılır ve ilgisiz parametreler korunur.
- Yol kaynağın kimliğini, sorgu aynı kaynağın görünümünü, parça belge içindeki konumu
  taşır; paylaşıldığında yeniden kurulması beklenmeyen durum adrese yazılmaz.
- Geçmişe ekleme ileri kayıtları atar; ara durumlar için o anki kaydı değiştirmek
  kullanıcının geri düğmesini korur.
- Gezinme olayı yalnızca geçmişte dolaşıldığında üretilir; ekleme ve değiştirme yapan kod
  arayüzü kendisi günceller.
- Her geçmiş kaydının serileştirilebilir bir durum nesnesi vardır; boyutu sınırlıdır ve
  adresten türetilebilen bilgi oraya konmaz.
- İstemci tarafı gezinmenin ürettiği her adres doğrudan istendiğinde sunucudan geçerli bir yanıt
  almalı ve bulunmayan adresler yine bulunamadı yanıtı vermelidir; kaydırma konumunun
  geri yüklenmesi ve eski isteklerin iptali de sayfanın sorumluluğuna geçer.

#### Sonraki Adım

Adres ve geçmiş, kullanıcının **istediği** görünümü tanımlar. Ancak görünümün ekranda ne
kadarının göründüğü, kabın ne kadar genişlediği ve ağacın ne zaman değiştiği bu bilgiden
okunamaz. Uzun ölçüm listesinde görsellerin yalnızca yaklaştıklarında yüklenmesi, bir
grafiğin kabı daraldığında yeniden çizilmesi, sonradan eklenen satırların bir kütüğe
işlenmesi — üçü de sürekli ölçüm yapmayı gerektirir. Bunu her karede elle yapmak hem
pahalıdır hem de ölçümün kendisi yerleşimi zorlar. Sonraki ders, tarayıcının bu üç soruyu
toplu ve eşzamansız biçimde yanıtlayan gözlemci ailesini ele alır.

### Gözlemciler

Source: https://academia.sh/tr/kurslar/tarayici-platformu/gozlemciler

Görünürlük, boyut ve ağaç değişimini toplu ve eşzamansız bildiren gözlemci ailesi; kesişim oranının hesabı, kök payı ve eşik, içerik ile kenarlık kutusu ayrımı, kayıtların toplu teslimi ve gözlemcinin yerleşim maliyetine katkısı.

Adres ve geçmiş, kullanıcının istediği görünümü tanımlar. Ancak görünümün ekranda ne
kadarının göründüğü, bir kabın ne kadar genişlediği ve ağacın ne zaman değiştiği bu
bilgiden okunamaz. Uzun ölçüm listesinde görsellerin yalnızca yaklaştıklarında yüklenmesi,
bir grafiğin kabı daraldığında yeniden çizilmesi, sonradan eklenen satırların işlenmesi —
üçü de sürekli ölçüm ister.

Bunu her karede elle yapmak iki nedenle yanlıştır. Ölçüm pahalıdır ve ölçümün kendisi
tarayıcıyı yerleşim hesabı yapmaya zorlar. Tarayıcı bu üç soruyu, kendi hesabını zaten
yaptığı anda yanıtlayan bir arayüz ailesiyle karşılar.

#### Ortak Biçim

Üç gözlemci de aynı kalıbı izler ve kalıp bir kez öğrenildiğinde üçü de aynı biçimde
kullanılır.

Bir **gözlemci (observer)** nesnesi, bir geri çağırım işleviyle kurulur. Kurulduğunda hiçbir
şey gözlemez; hangi hedeflerin izleneceği ayrıca bildirilir. Aynı gözlemci birden çok
hedefi izleyebilir ve her hedef için ayrı seçenek verilebilir.

Geri çağırım, tek tek olaylarla değil **kayıt dizisiyle** çağrılır. Aralarında geçen sürede
biriken bütün değişiklikler tek çağrıda teslim edilir.

İzleme iki işlemle sonlandırılır: tek bir hedefin izlenmesi bırakılır ya da bütün hedeflerin
izlenmesi kesilir. Kesişim ve değişim gözlemcilerinde ayrıca henüz teslim edilmemiş kayıtlar elle
alınabilir; boyut gözlemcisinde bu işlem yoktur. Bir bileşen ağaçtan çıkarıldığında gözlemcisini
kesmek zorundadır; kesilmeyen gözlemci, hedefine erişim tuttuğu için Eşzamansız JavaScript ve
Çalışma Zamanı kursunda tanımlanan sızıntının kaynağı olur.

Bu kalıbın olay yerine seçilmesinin nedeni, olay modelinin sıklık denetimi
sunmamasıdır. Kaydırma olayı her kaydırma adımında üretilir ve dinleyicisi her seferinde
ölçüm yapmak zorunda kalır. Gözlemci ise ölçümü tarayıcının kendi hesabından alır ve
yalnızca sonuç değiştiğinde bildirir.

#### Kesişim Gözlemcisi

**Kesişim gözlemcisi (intersection observer)** bir hedefin, bir kök dikdörtgeniyle ne
kadar kesiştiğini bildirir. Kök varsayılan olarak görünüm alanıdır; kaydırılabilir bir kap
da kök olarak verilebilir.

İki ayar davranışı belirler. **Kök payı (root margin)** kök dikdörtgenini büyütür ya da
küçültür; büyütmek, hedef henüz görünmeden bildirim almayı sağlar. **Eşik (threshold)**
bildirimin hangi kesişim oranlarında üretileceğini söyler.

```js
// kesisim.mjs — kesisim oraninin ve esik gecisinin hesabi
const dikdortgen = (ust, alt, sol = 0, sag = 360) => ({ ust, alt, sol, sag });
const alan = (r) => Math.max(0, r.alt - r.ust) * Math.max(0, r.sag - r.sol);

const kesisim = (a, b) => dikdortgen(
  Math.max(a.ust, b.ust), Math.min(a.alt, b.alt),
  Math.max(a.sol, b.sol), Math.min(a.sag, b.sag),
);

// Pay, kok dikdortgenini her yonde genisletir ya da daraltir.
const paylaKok = (kok, pay) => dikdortgen(
  kok.ust - pay.ust, kok.alt + pay.alt, kok.sol - pay.sol, kok.sag + pay.sag,
);

const oran = (hedef, kok) => alan(kesisim(hedef, kok)) / alan(hedef);

const gorunumAlani = dikdortgen(0, 800);
const onYukleme = { ust: 0, alt: 300, sol: 0, sag: 0 };

const satirlar = [
  ["T-01", dikdortgen(-40, 60)],    // ust kenardan disari tasmis
  ["T-02", dikdortgen(300, 400)],   // tumuyle icerde
  ["T-03", dikdortgen(760, 860)],   // alt kenarda
  ["T-04", dikdortgen(900, 1000)],  // gorunum disinda, pay icinde
  ["T-05", dikdortgen(1200, 1300)], // pay disinda
];

const ESIK = 0.25;
console.log("satir  oran(paysiz)  oran(pay:alt 300)  esik 0.25");
for (const [ad, hedef] of satirlar) {
  const a = oran(hedef, gorunumAlani);
  const b = oran(hedef, paylaKok(gorunumAlani, onYukleme));
  console.log(
    ad,
    a.toFixed(2).padStart(12),
    b.toFixed(2).padStart(18),
    (a >= ESIK ? "  gecti" : "  gecmedi").padEnd(9),
    b >= ESIK ? "| payla gecti" : "| payla da gecmedi",
  );
}
console.log("genisletilmis kok:", JSON.stringify(paylaKok(gorunumAlani, onYukleme)));
```

```
satir  oran(paysiz)  oran(pay:alt 300)  esik 0.25
T-01         0.60               0.60   gecti   | payla gecti
T-02         1.00               1.00   gecti   | payla gecti
T-03         0.40               1.00   gecti   | payla gecti
T-04         0.00               1.00   gecmedi | payla gecti
T-05         0.00               0.00   gecmedi | payla da gecmedi
genisletilmis kok: {"ust":0,"alt":1100,"sol":0,"sag":360}
```

Oran, hedefin **kendi alanına** göre hesaplanır; kökün alanına göre değil. Bu nedenle
görünüm alanından büyük bir öge tamamen ekranı kaplasa da oranı bire ulaşmaz.

Payın etkisi dördüncü satırda görünür. Ekranın 100 piksel altında duran satır paysız
hesapta hiç kesişmez; alt paya 300 piksel eklendiğinde tümüyle kesişir sayılır. Görselin
yüklenmesi bu bildirimle başlatıldığında, kullanıcı satıra ulaştığında görsel hazırdır.
Payın büyüklüğü bir ödünleşimdir: küçük pay boş kutular, büyük pay gereksiz indirme
üretir.

Eşik değerleri liste olarak verilebilir; her eşik geçişinde bildirim üretilir. Yalnız
"göründü ya da kayboldu" sorusu için tek bir sıfır eşiği yeter. Bir reklamın ya da içeriğin
ne kadar süre ne oranda görüldüğünü ölçmek için birden çok eşik gerekir.

İki davranış sık şaşırtır. Gözlemci, hedef izlenmeye başlandığında **ilk durumu da**
bildirir; ekranda zaten görünen ögeler için bir çağrı üretilir. Ayrıca gözlemci "görünür
mü" değil "kesişiyor mu" sorusunu yanıtlar: saydamlığı sıfır olan ya da başka bir ögenin
altında kalan hedef, kesişiyorsa görünür sayılır.

#### Boyut Gözlemcisi

**Boyut gözlemcisi (resize observer)** bir ögenin ölçüsü değiştiğinde bildirir. Pencere
boyutu olayından ayrıldığı nokta budur: pencere değişmeden de bir kabın ölçüsü değişebilir —
yan panel açıldığında, yazı tipi yüklendiğinde, kardeş bir öge büyüdüğünde.

Bildirim, CSS ile Görsel Sunum kursundaki Kutu Modeli dersinde tanımlanan kutuları ayrı
ayrı verir: içerik kutusu ile kenarlık kutusu farklı sayılardır ve hangisinin okunacağı
hesaba bağlıdır. Grafiğin çizim alanı içerik kutusudur; kabın kapladığı yer kenarlık
kutusudur.

Bu gözlemcinin kendine özgü bir tuzağı vardır. Geri çağırım içinde gözlenen ögenin
boyutunu değiştiren kod, yeni bir bildirim üretir; bildirim yeniden boyut değiştirir ve
döngü kurulur. Tarayıcı bu döngüyü saptayıp bir hata bildirir ve o kare için çizimi
sürdürür. Çözüm, geri çağırımda ölçüye bağlı olmayan bir değişiklik yapmak ya da yeni
ölçüyü bir koşulla sınırlamaktır.

#### Değişim Gözlemcisi

**Değişim gözlemcisi (mutation observer)** belge ağacındaki değişiklikleri bildirir: çocuk
eklenmesi ve çıkarılması, öznitelik değişimi, metin düğümünün içeriğinin değişmesi. Hangi
türlerin izleneceği seçeneklerle bildirilir ve alt ağacın tamamı kapsama alınabilir.

```js
// kayit.mjs — degisim kayitlarinin biriktirilip toplu teslim edilmesi
class DegisimGozlemcisi {
  constructor(geriCagirim) { this.geriCagirim = geriCagirim; this.kuyruk = []; this.hedefler = []; }
  gozle(hedef, secenek) { this.hedefler.push({ hedef, secenek }); }
  kaydet(kayit) {
    const eslesen = this.hedefler.find(
      (h) => h.hedef === kayit.hedef && h.secenek[kayit.tur] === true,
    );
    if (eslesen === undefined) return;                 // gozlenmeyen tur kaydedilmez
    if (kayit.tur === "oznitelik" && eslesen.secenek.eskiDeger !== true) kayit = { ...kayit, eski: undefined };
    this.kuyruk.push(kayit);
  }
  bosalt() {                                            // mikro gorev sinirinda calisir
    if (this.kuyruk.length === 0) return;
    const kayitlar = this.kuyruk;
    this.kuyruk = [];
    this.geriCagirim(kayitlar);
  }
}

let cagriSayisi = 0;
const gozlemci = new DegisimGozlemcisi((kayitlar) => {
  cagriSayisi += 1;
  console.log(`geri cagirim ${cagriSayisi}: ${kayitlar.length} kayit`);
  for (const k of kayitlar) console.log("   ", JSON.stringify(k));
});

const liste = { ad: "ul#olcumler" };
const baslik = { ad: "h2#baslik" };
gozlemci.gozle(liste, { cocukListesi: true, oznitelik: true, eskiDeger: true });
gozlemci.gozle(baslik, { cocukListesi: true });        // oznitelik gozlenmiyor

// Tek bir gorev icinde uc satir eklenip bir oznitelik degistiriliyor.
for (const kod of ["T-05", "T-06", "T-07"])
  gozlemci.kaydet({ tur: "cocukListesi", hedef: liste, eklenen: kod });
gozlemci.kaydet({ tur: "oznitelik", hedef: liste, ad: "data-suzgec", eski: "hepsi" });
gozlemci.kaydet({ tur: "oznitelik", hedef: baslik, ad: "class", eski: "acik" });
console.log("teslim oncesi kuyruk:", gozlemci.kuyruk.length);
gozlemci.bosalt();

// Sonraki gorevde tek degisiklik: ayri bir geri cagirim.
gozlemci.kaydet({ tur: "cocukListesi", hedef: liste, eklenen: "T-08" });
gozlemci.bosalt();
gozlemci.bosalt();                                      // bos kuyruk cagri uretmez
console.log("toplam geri cagirim:", cagriSayisi);
```

```
teslim oncesi kuyruk: 4
geri cagirim 1: 4 kayit
    {"tur":"cocukListesi","hedef":{"ad":"ul#olcumler"},"eklenen":"T-05"}
    {"tur":"cocukListesi","hedef":{"ad":"ul#olcumler"},"eklenen":"T-06"}
    {"tur":"cocukListesi","hedef":{"ad":"ul#olcumler"},"eklenen":"T-07"}
    {"tur":"oznitelik","hedef":{"ad":"ul#olcumler"},"ad":"data-suzgec","eski":"hepsi"}
geri cagirim 2: 1 kayit
    {"tur":"cocukListesi","hedef":{"ad":"ul#olcumler"},"eklenen":"T-08"}
toplam geri cagirim: 2
```

Dört davranış çıktıda okunur. Kayıtlar **biriktirilir**; dört değişiklik tek çağrıda
teslim edilmiştir. Teslim, olaylar gibi anında değil, çalışan kod bittikten sonra
gerçekleşir — Eşzamansız JavaScript ve Çalışma Zamanı kursundaki mikro görev sınırı burada
işler. İzlenmeyen tür hiç kaydedilmez: başlık ögesinin öznitelik değişimi kuyruğa
girmemiştir. Boş kuyruk çağrı üretmez.

Eski değerin kayıtta bulunması ayrıca istenir. İstenmediğinde kayıt yalnızca "bu öznitelik
değişti" der; önceki değer kaybolmuştur. Bu ayrım bellek maliyeti içindir ve varsayılan
olarak kapalıdır.

Değişim gözlemcisi son çare olarak kullanılır. Kendi yazdığı ağacı değiştiren kod, neyi
değiştirdiğini zaten bilir; ağacı gözlemek yerine değişikliği yapan yerde bildirimde
bulunmak hem ucuz hem açıktır. Gözlemci, denetim dışındaki bir kodun ürettiği değişikliği
izlemek gerektiğinde anlamlıdır.

#### Ölçümün Maliyeti

Gözlemcilerin ortak gerekçesi burada toplanır. Bir ögenin ölçüsünü ya da konumunu doğrudan
sorgulamak, tarayıcıyı bekleyen bütün biçem ve yerleşim hesaplarını **o anda** yapmaya
zorlar. Döngü içinde sırayla ölçüp değiştiren kod bu zorlamayı her turda tetikler.

Gözlemciler bu zorlamayı ortadan kaldırır: ölçüm, tarayıcının kendi hesabını yaptığı anda
alınır ve sonuç geri çağırıma hazır olarak gelir. Ölçüm ile değiştirmenin ayrı aşamalara
bölünmesi ise sonraki dersin konusudur.

#### Özet

- Üç gözlemci ortak bir kalıp izler: geri çağırımla kurulur, hedefler ayrıca bildirilir,
  teslim tek tek değil kayıt dizisiyle yapılır ve izleme açıkça kesilir.
- Kesişim oranı hedefin kendi alanına göre hesaplanır; kök payı bildirimi erkene çeker,
  eşik listesi hangi oranlarda bildirim üretileceğini belirler.
- Kesişim gözlemcisi izleme başladığında ilk durumu da bildirir ve görünürlüğü değil
  kesişimi ölçer.
- Boyut gözlemcisi içerik ve kenarlık kutularını ayrı bildirir; geri çağırımda boyutu
  değiştiren kod döngü üretir.
- Değişim kayıtları biriktirilip mikro görev sınırında toplu teslim edilir; izlenmeyen tür
  kaydedilmez ve eski değer ayrıca istenmedikçe taşınmaz.
- Gözlemciler ölçümü tarayıcının kendi hesabından aldığı için doğrudan sorgulamanın
  yarattığı yerleşim zorlamasını ortadan kaldırır.

#### Sonraki Adım

Gözlemciler ölçümün ne zaman alınacağını çözer, ancak değişikliğin ne zaman uygulanacağını
açık bırakır. Ölçüm listesi kaydırılırken başlığın konumunu güncelleyen, bir çubuğu
büyüten ya da sayacı ilerleten kod, bunu rastgele bir anda yaparsa tarayıcının çizim
düzenine ters düşer: kare atlanır, hareket kesikleşir. Tarayıcının her kareyi hangi
sırayla hazırladığı tanımlıdır ve bu sıraya yerleşmenin bir yolu vardır. Sonraki ders kare
kavramını, zamanlayıcıların neden kare hizasında olmadığını ve okuma ile yazmanın neden
ayrı aşamalara bölündüğünü ele alır.

### Zamanlama ve Çizim Döngüsü

Source: https://academia.sh/tr/kurslar/tarayici-platformu/zamanlama-ve-cizim-dongusu

Tarayıcının kare hazırlama sırası ve güncellemenin bu sıraya yerleştirilmesi; kare bütçesi, sabit gecikmeli zamanlayıcının kaydırdığı hiza, kare geri çağırımının kuralları, okuma ile yazmanın ayrılması ve uzun işin bölünmesi.

Gözlemciler ölçümün ne zaman alınacağını çözer, değişikliğin ne zaman uygulanacağını açık
bırakır. Ölçüm listesi kaydırılırken başlığın konumunu güncelleyen ya da bir çubuğu büyüten
kod, bunu rastgele bir anda yaparsa tarayıcının çizim düzenine ters düşer.

Tarayıcı ekranı sürekli değil, **kare kare** yeniler. Her karede belirli bir sırayı izler
ve bu sıra tanımlıdır. Güncellemeyi bu sıraya yerleştirmek, hareketin düzgün görünmesinin
tek yoludur.

#### Karenin Anatomisi

Bir kare şu aşamalardan geçer: birikmiş girdi olayları işlenir, süresi dolan zamanlayıcılar
çalışır, kare geri çağırımları çağrılır, biçem hesaplanır, yerleşim yapılır, boyama yapılır
ve katmanlar birleştirilir.

Bu sıradan iki sonuç çıkar. Birincisi, kare geri çağırımları **biçem ve yerleşimden önce**
çalışır; orada yapılan değişiklik aynı karede ekrana yansır. İkincisi, bir karede yapılan
bütün işin toplamı, karenin süresini aşmamalıdır.

Karenin süresi ekranın yenileme hızına bağlıdır ve her cihazda aynı değildir. Saniyede 60
kare varsayımıyla bir kare 16,67 milisaniye sürer; daha hızlı ekranlarda bu süre kısalır.
Bu nedenle yazılan kod belirli bir süreyi varsaymak yerine, tarayıcının verdiği zaman
damgasına dayanmalıdır.

#### Zamanlayıcı ile Kare Hizası

Yaygın bir yaklaşım, güncellemeyi sabit gecikmeli bir zamanlayıcıya bağlamaktır. Bu
yaklaşımın sorunu, gecikmenin **işin bitişinden** sayılmasıdır: her turda işin süresi
kadar bir kayma birikir.

```js
// kare.mjs — sabit gecikmeli zamanlayici ile kare hizasindaki geri cagirimin karsilastirmasi
const KARE = 1000 / 60;              // saniyede 60 kare varsayimiyla kare suresi (ms)
const IS = 4;                        // her guncellemenin isledigi sure (ms)
const ADIM = 8;

const kareNo = (t) => Math.floor(t / KARE);

// Zamanlayici: her tur, isin bitisinden 16 ms sonrasina yeniden kurulur.
let t = 0;
const zamanlayici = [];
for (let i = 0; i < ADIM; i += 1) {
  zamanlayici.push(t);
  t += IS + 16;
}

// Kare hizasi: geri cagirim kare basinda calisir, is suresi bir sonraki hedefi kaydirmaz.
const hizali = Array.from({ length: ADIM }, (_, i) => i * KARE);

const sayim = (anlar) => {
  const kutu = new Map();
  for (const an of anlar) kutu.set(kareNo(an), (kutu.get(kareNo(an)) ?? 0) + 1);
  return kutu;
};

console.log("adim  zamanlayici(ms)  kare   kare hizasi(ms)  kare");
for (let i = 0; i < ADIM; i += 1)
  console.log(
    String(i + 1).padStart(4),
    zamanlayici[i].toFixed(2).padStart(15), String(kareNo(zamanlayici[i])).padStart(6),
    hizali[i].toFixed(2).padStart(17), String(kareNo(hizali[i])).padStart(6),
  );

const bos = (anlar) => {
  const kutu = sayim(anlar);
  const son = kareNo(anlar[anlar.length - 1]);
  let n = 0;
  for (let k = 0; k <= son; k += 1) if ((kutu.get(k) ?? 0) === 0) n += 1;
  return { son, bos: n };
};
for (const [ad, anlar] of [["zamanlayici", zamanlayici], ["kare hizasi", hizali]]) {
  const { son, bos: n } = bos(anlar);
  console.log(`${ad.padEnd(11)}: ${ADIM} guncelleme, kare 0..${son} araligina yayildi, bos kare: ${n}`);
}
console.log("kare butcesi:", KARE.toFixed(2), "ms | is:", IS, "ms | kalan:", (KARE - IS).toFixed(2), "ms");
```

```
adim  zamanlayici(ms)  kare   kare hizasi(ms)  kare
   1            0.00      0              0.00      0
   2           20.00      1             16.67      1
   3           40.00      2             33.33      2
   4           60.00      3             50.00      3
   5           80.00      4             66.67      4
   6          100.00      6             83.33      5
   7          120.00      7            100.00      6
   8          140.00      8            116.67      7
zamanlayici: 8 guncelleme, kare 0..8 araligina yayildi, bos kare: 1
kare hizasi: 8 guncelleme, kare 0..7 araligina yayildi, bos kare: 0
kare butcesi: 16.67 ms | is: 4 ms | kalan: 12.67 ms
```

Kayma altıncı adımda görünür hâle gelir: beşinci kare hiçbir güncelleme almaz, altıncı kare
bir sonraki güncellemeyi alır. Kullanıcının gördüğü şey donma değil, düzensizliktir —
hareket bir an duraksar. Aynı sayıda güncelleme kare hizasında yapıldığında boş kare kalmaz.

Sabit gecikmeli zamanlayıcının ikinci sorunu, karesi olmayan anlarda da çalışmasıdır. Sekme
arka plana alındığında ekran yenilenmez; zamanlayıcı yine de tetiklenir ve hiç görünmeyecek
kareler hesaplanır. Kare geri çağırımı bu durumda çağrılmaz ve iş kendiliğinden durur.

#### Kare Geri Çağırımının Kuralları

Kare geri çağırımı **tek seferliktir**: bir sonraki kare için yeniden kaydolmak gerekir.
Süregelen bir hareket, geri çağırımın içinden kendini yeniden kaydeder; durdurmak, kaydı
yenilememek ya da bekleyen kaydı iptal etmektir.

Geri çağırım, karenin zaman damgasını argüman olarak alır. Hareketin ilerlemesi bu damgadan
hesaplanır: "her çağrıda bir piksel" yerine "geçen süreye göre şu kadar piksel". Birinci
biçim, kare hızı değiştiğinde hareketin hızını da değiştirir; ikinci biçim cihazdan
bağımsızdır. Aynı damga o karedeki bütün geri çağırımlara verilir, böylece aynı karede
güncellenen ögeler birbiriyle tutarlı kalır.

Kullanıcının hareket tercihi ayrı bir kuraldır. İşletim sisteminde hareketin azaltılması
istenmişse, bu tercih biçem tarafında sorgulanabilir ve program tarafında da okunabilir;
tercih varsa hareket kaldırılır ya da anlık geçişe indirilir.

#### Okuma ve Yazmayı Ayırmak

Kare içinde yapılan işin ucuz kalması için ikinci bir kural gerekir. Bir ögenin ölçüsünü
sorgulamak, bekleyen bütün yazmaların o anda işlenmesini zorlar; Gözlemciler dersinde
anılan zorlama budur. Ölçüm ile yazmanın sırayla yapılması bu zorlamayı her turda tetikler.

```js
// okuma.mjs — okuma ile yazmanin siralanmasinin zorladigi yerlesim sayisi
function kurulum() {
  return {
    cubuklar: [40, 55, 30, 72, 61].map((g, i) => ({ ad: `T-0${i + 1}`, genislik: g })),
    kirli: false,          // bekleyen yazma var mi
    yerlesim: 0,           // zorlanmis yerlesim hesabi sayisi
  };
}
const oku = (d, cubuk) => {
  if (d.kirli) { d.yerlesim += 1; d.kirli = false; }   // olcum, bekleyen yazmalari isletir
  return cubuk.genislik;
};
const yaz = (d, cubuk, deger) => { cubuk.genislik = deger; d.kirli = true; };

// Birinci yol: her cubukta once oku sonra yaz.
const a = kurulum();
for (const cubuk of a.cubuklar) yaz(a, cubuk, oku(a, cubuk) + 10);
console.log("araya girmis :", a.yerlesim, "zorlanmis yerlesim |",
  a.cubuklar.map((c) => c.genislik).join(" "));

// Ikinci yol: once butun olcumler, sonra butun yazmalar.
const b = kurulum();
const olculen = b.cubuklar.map((cubuk) => oku(b, cubuk));
b.cubuklar.forEach((cubuk, i) => yaz(b, cubuk, olculen[i] + 10));
console.log("ayrilmis     :", b.yerlesim, "zorlanmis yerlesim |",
  b.cubuklar.map((c) => c.genislik).join(" "));
console.log("sonuclar ayni:", JSON.stringify(a.cubuklar) === JSON.stringify(b.cubuklar));
```

```
araya girmis : 4 zorlanmis yerlesim | 50 65 40 82 71
ayrilmis     : 0 zorlanmis yerlesim | 50 65 40 82 71
sonuclar ayni: true
```

İki yol aynı sonucu üretir; maliyetleri farklıdır. Ayrılmış sırada ölçümler tek seferde
alınır, yazmalar birikir ve yerleşim karenin kendi aşamasında bir kez yapılır.

Buradaki sayı yine bir başarım ölçümü değil, **çağrı sayımıdır**; gerçek maliyet belgeye ve
biçeme bağlıdır. Sabit olan şey, tetiklenen hesap sayısının dörtten sıfıra inmesidir.
Kural, öge sayısı arttıkça daha da belirginleşir: ayrılmış sıra sabit sayıda hesap üretir,
araya girmiş sıra öge sayısıyla doğru orantılı.

#### Uzun İşi Bölmek

Kare bütçesini aşan tek bir iş, o karede yapılacak her şeyi bekletir: girdi işlenmez,
hareket ilerlemez. Bin ölçümü tek turda süzen bir işlev bunu yapar.

Çözüm işi bölmektir. Parça işlenir, denetim çalışma zamanına geri verilir, sonraki parça
bir sonraki turda işlenir. Böylece girdi olayları aradaki boşluklarda işlenir ve arayüz
yanıt vermeyi sürdürür. Bölmenin ölçüsü sabit bir parça sayısı değil, geçen süredir: her
turda süre ölçülür ve bütçe dolduğunda tur bitirilir.

Kimi işler karede yapılmak zorunda değildir — kütüğe yazma, önbellek temizliği, sonraki
sayfanın hazırlanması. Bunlar için tarayıcı **boşta çalışma** sınıfından bir geri çağırım
sunar: kare işini bitirdiğinde kalan süre varsa çağrılır ve kalan süreyi bildirir. Boşta
çalışan geri çağırıma verilen iş, hiç çalışmama olasılığına karşı bir zaman aşımıyla
sınırlandırılır.

Ağır hesabın bir başka çözümü, işi ana iş parçacığından tümüyle çıkarmaktır; bu Web
İşçileri dersinin konusudur.

#### Özet

- Tarayıcı ekranı kare kare yeniler; kare geri çağırımları biçem ve yerleşimden önce
  çalıştığı için orada yapılan değişiklik aynı karede görünür.
- Sabit gecikmeli zamanlayıcı, gecikmeyi işin bitişinden saydığı için kayma biriktirir ve
  kimi kareler güncelleme almaz; kare geri çağırımı hizayı korur.
- Kare geri çağırımı tek seferliktir, zaman damgası alır ve ekranı yenilenmeyen sekmede
  çağrılmaz; hareket geçen süreye göre hesaplanır.
- Ölçüm ile yazmanın sırayla yapılması her turda yerleşim hesabı zorlar; önce bütün
  ölçümler sonra bütün yazmalar yapıldığında bu zorlama ortadan kalkar.
- Kare bütçesini aşan iş parçalara bölünür ve parçalar arasında denetim çalışma zamanına
  geri verilir; ertelenebilir işler boşta çalışma geri çağırımına bırakılır.

#### Sonraki Adım

Kare içinde yapılan işin bir bölümü metin üretmektir: ölçüm değerinin yanına birimi
yazmak, zaman damgasını okunur bir tarihe çevirmek, listeyi ada göre sıralamak. Bu
işlemlerin hiçbiri evrensel değildir. Ondalık ayırıcı her yerde aynı karakter değildir,
tarih alanlarının sırası dile göre değişir, harflerin sıralaması abeceye bağlıdır ve
Türkçede noktalı ile noktasız harf ayrı harflerdir. Elle yazılmış biçimleme kodu bu
farkları taşıyamaz. Sonraki ders, tarayıcının bu işleri yerel ayara göre yapan arayüzünü
ve o arayüzün kurulum maliyetini ele alır.

### Uluslararasılaştırma API'si

Source: https://academia.sh/tr/kurslar/tarayici-platformu/uluslararasilastirma-apisi

Sayı, tarih ve sıralamanın yerel ayara göre yapılması; yerel ayar etiketinin yapısı, biçimlemenin parçalara ayrılması, saat diliminin rolü, harf sıralamasının abeceye bağlılığı ve biçimleyicinin kurulum maliyeti.

Kare içinde yapılan işin bir bölümü metin üretmektir: ölçüm değerinin yanına birimi
yazmak, zaman damgasını okunur bir tarihe çevirmek, listeyi ada göre sıralamak. Bu
işlemlerin hiçbiri evrensel değildir.

Ondalık ayırıcı her yerde aynı karakter değildir, tarih alanlarının sırası dile göre
değişir, harflerin sıralaması abeceye bağlıdır ve Türkçede noktalı ile noktasız harf ayrı
harflerdir. Elle yazılan biçimleme kodu bu farkları taşıyamaz. Tarayıcı bunun için, dil
verisini kendi taşıyan bir arayüz ailesi sunar.

Bu dersteki çıktılar bir çalışma zamanında üretilmiştir. Biçimlenmiş metinlerin **tam
karakter dizisi** çalışma zamanının taşıdığı dil verisine bağlıdır ve sürümler arasında
değişebilir; değişmeyen şey işlemin kendisidir. Bu ayrım dersin son bölümünde bir kurala
dönüşür.

#### Yerel Ayar Etiketi

Bütün bu arayüzler bir **yerel ayar (locale)** etiketi alır. Etiket kısa çizgiyle ayrılmış
alt etiketlerden oluşur: dil, isteğe bağlı yazı sistemi, isteğe bağlı bölge. `tr` yalnız
dili, `tr-TR` dil ile bölgeyi, `sr-Latn-RS` üçünü birden bildirir.

Bölge alt etiketi dili değil **kuralları** seçer: aynı dilin iki bölgesinde para birimi,
ölçü sistemi ve tarih sırası ayrışabilir. Etiketin sonuna eklenen uzantılarla takvim,
numaralandırma sistemi ve sıralama düzeni de istenebilir.

Kullanıcının tercihi tarayıcıdan okunur ve bir **liste** olarak gelir: birinci tercih
desteklenmiyorsa ikincisi denenir. Arayüzlere de tek etiket yerine liste verilebilir;
seçim, sağlanan verilerle kesişim alınarak yapılır. Hangi ayarın seçildiği ve hangi
seçeneklerin geçerli olduğu, biçimleyiciden geri sorulabilir.

#### Sayı Biçimleme

```js
// sayi.mjs — ayni sayinin yerel ayara gore bicimlenmesi
const deger = -1234.567;

for (const yerel of ["tr-TR", "en-US", "de-DE"]) {
  const bicim = new Intl.NumberFormat(yerel, { maximumFractionDigits: 2 });
  console.log(yerel, "->", bicim.format(deger));
}

const sicaklik = new Intl.NumberFormat("tr-TR", {
  style: "unit", unit: "celsius", unitDisplay: "short", maximumFractionDigits: 1,
});
const oran = new Intl.NumberFormat("tr-TR", { style: "percent", maximumFractionDigits: 1 });
const derli = new Intl.NumberFormat("tr-TR", { notation: "compact" });
const derliMetin = derli.format(48210);

console.log("sicaklik:", sicaklik.format(-4.23));
console.log("oran    :", oran.format(0.0731));
console.log("derli   :", derliMetin, "| kod noktalari:",
  [...derliMetin].map((c) => "U+" + c.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")).join(" "));

// Parcalara ayirmak, bicimli metnin icinden alan secmeyi saglar.
console.log(JSON.stringify(sicaklik.formatToParts(-4.23)));
```

```
tr-TR -> -1.234,57
en-US -> -1,234.57
de-DE -> -1.234,57
sicaklik: -4,2°C
oran    : %7,3
derli   : 48 B | kod noktalari: U+0034 U+0038 U+00A0 U+0042
[{"type":"minusSign","value":"-"},{"type":"integer","value":"4"},{"type":"decimal","value":","},{"type":"fraction","value":"2"},{"type":"unit","value":"°C"}]
```

İlk üç satır ondalık ve binlik ayırıcıların yer değiştirdiğini gösterir; aynı sayı üç
yazımla görünür. Bir sayıyı noktayla birleştirip ekrana yazan kod, kullanıcının binlik
ayırıcı sandığı bir noktayla karşılaşabilir.

Yüzde satırı ikinci bir farkı taşır: Türkçede işaret sayının **önüne** gelir. Birim ve
işaretin nereye konacağı da dil verisinden gelir; elle birleştirme bu bilgiyi kaybeder.

Derli gösterimin kod noktaları üçüncü bir uyarı taşır. Sayı ile kısaltma arasındaki
karakter sıradan boşluk değil, Metin Etiketleri dersinde tanımlanan **bölünmez boşluktur**;
kod noktası listesinde `U+00A0` olarak görünür. Metni boşluktan bölerek sayıyı geri almaya
çalışan kod bu karakterle karşılaşır. Biçimlenmiş metnin çözümlenmemesi gerektiğinin somut
kanıtı budur.

Son satır en kullanışlı işlemi gösterir. Biçimlenmiş metin parçalarına ayrılabilir ve her
parçanın türü bildirilir. Böylece tam sayı kısmı büyük, ondalık kısmı küçük yazılabilir ya
da birim ayrı bir ögeye alınabilir; bunun için metni bölmeye çalışmak gerekmez.

Biçimlemenin girdisi de dikkat ister. Sayı biçimleyici, dizgi değil sayı bekler; Depolama
API'leri dersinde anılan tip kaybı burada görünür hâle gelir.

#### Tarih, Saat ve Saat Dilimi

```js
// tarih.mjs — ayni anin yerel ayara ve saat dilimine gore bicimlenmesi
const an = new Date("2024-02-11T06:05:00Z");   // evrensel zamanda sabit bir an

for (const [yerel, dilim] of [["tr-TR", "Europe/Istanbul"], ["en-US", "America/New_York"]]) {
  const bicim = new Intl.DateTimeFormat(yerel, {
    dateStyle: "long", timeStyle: "short", timeZone: dilim,
  });
  console.log(yerel.padEnd(6), dilim.padEnd(17), bicim.format(an));
}

// Alanlari ayri ayri istemek ve parcalara ayirmak
const alanli = new Intl.DateTimeFormat("tr-TR", {
  weekday: "long", day: "numeric", month: "long", timeZone: "Europe/Istanbul",
});
console.log("alanli  :", alanli.format(an));
console.log("parcalar:", alanli.formatToParts(an).map((p) => `${p.type}=${p.value}`).join(" "));

// Goreli zaman: birim ve sayi verilir, metin yerel ayardan gelir.
const goreli = new Intl.RelativeTimeFormat("tr-TR", { numeric: "auto" });
for (const [sayi, birim] of [[-1, "day"], [-3, "hour"], [0, "day"], [2, "week"]])
  console.log("goreli  :", goreli.format(sayi, birim));
```

```
tr-TR  Europe/Istanbul   11 Şubat 2024 09:05
en-US  America/New_York  February 11, 2024 at 1:05 AM
alanli  : 11 Şubat Pazar
parcalar: day=11 literal=  month=Şubat literal=  weekday=Pazar
goreli  : dün
goreli  : 3 saat önce
goreli  : bugün
goreli  : 2 hafta sonra
```

İlk iki satır aynı anı gösterir. Ölçüm kaydında saklanan şey evrensel zamandaki tek bir
andır; kullanıcıya gösterilen ise o anın kullanıcının saat dilimindeki karşılığıdır. Saat
diliminin biçimleyiciye açıkça verilmesi, kaydın hangi dilimde okunduğunu belirsiz
bırakmaz. Verilmediğinde cihazın dilimi kullanılır ve iki kullanıcı aynı kaydı farklı
saatlerle görür.

Alan sırası da dilden gelir: Türkçede gün, ay, gün adı; başka bir dilde bambaşka bir sıra.
Parça listesindeki ayraçlar bile dil verisinin bir parçasıdır; bu, tarihi elle
birleştirmenin neden çalışmadığını gösterir.

Göreli zaman biçimleyicisi ayrı bir sorunu çözer. "Kaç birim önce" hesabı programın
işidir; o hesabın metne dönüşmesi dil verisinin. Otomatik kip, karşılığı olan yerlerde
sayı yerine sözcük kullanır — çıktıdaki "dün" ve "bugün" bunun sonucudur.

#### Sıralama ve Karşılaştırma

Dizgi karşılaştırmasının varsayılan biçimi kod noktalarını karşılaştırır. Karakter
Kodlamaları dersinde tanımlanan bu sıra, hiçbir dilin abecesine karşılık gelmez.

```js
// siralama.mjs — kod noktasi sirasi ile yerel ayara duyarli siralama
const istasyonlar = ["Iğdır", "İzmir", "Isparta", "Şanlıurfa", "Sivas", "Çorum", "Ordu"];

console.log("kod noktasi:", istasyonlar.toSorted().join(" "));
console.log("tr-TR      :", istasyonlar.toSorted(new Intl.Collator("tr").compare).join(" "));
console.log("en-US      :", istasyonlar.toSorted(new Intl.Collator("en").compare).join(" "));

// Duyarlilik: buyuk-kucuk ve aksan farkinin esitlik sayilip sayilmayacagi
const gevsek = new Intl.Collator("tr", { sensitivity: "base" });
console.log("'Izmir' ile 'İZMİR' esit mi:", gevsek.compare("Izmir", "İZMİR") === 0);

// Sayisal siralama: dizgi icindeki sayilar sayi olarak karsilastirilir
const kodlar = ["T-2", "T-10", "T-1", "T-21"];
console.log("dizgi olarak:", kodlar.toSorted(new Intl.Collator("tr").compare).join(" "));
console.log("sayi olarak :", kodlar.toSorted(new Intl.Collator("tr", { numeric: true }).compare).join(" "));

// Cogul kurallari: hangi kategoriye dustugu sorulur, metin yerelden gelir
const cogul = new Intl.PluralRules("tr");
console.log("cogul tr    :", [0, 1, 2, 11].map((n) => `${n}=${cogul.select(n)}`).join(" "));
console.log("cogul en    :", [0, 1, 2, 11].map((n) => `${n}=${new Intl.PluralRules("en").select(n)}`).join(" "));
```

```
kod noktasi: Isparta Iğdır Ordu Sivas Çorum İzmir Şanlıurfa
tr-TR      : Çorum Iğdır Isparta İzmir Ordu Sivas Şanlıurfa
en-US      : Çorum Iğdır Isparta İzmir Ordu Şanlıurfa Sivas
'Izmir' ile 'İZMİR' esit mi: false
dizgi olarak: T-1 T-10 T-2 T-21
sayi olarak : T-1 T-2 T-10 T-21
cogul tr    : 0=other 1=one 2=other 11=other
cogul en    : 0=other 1=one 2=other 11=other
```

İlk satır kod noktası sırasının sonucudur: aksanlı harfler abecenin dışına atılmış, `Ç` ile
`Ş` listenin sonuna düşmüştür. İkinci satır Türkçe abeceyi izler.

Üçüncü satır ince bir ayrım taşır. Türkçe ile İngilizce sıralama yalnızca `Ş` harfinde
ayrışır: Türkçede `Ş` ayrı bir harftir ve `S` den sonra gelir, İngilizce sıralamada `S` in
bir çeşitlemesi sayılır ve `Şanlıurfa` `Sivas` tan önce gelir. Aynı veri, kullanıcının
diline göre farklı sıralanır.

Dördüncü satır Türkçenin bilinen tuzağını gösterir. En gevşek duyarlılıkta bile noktalı ve
noktasız harf **eşit sayılmaz**, çünkü Türkçede bunlar aynı harfin iki biçimi değil, iki
ayrı harftir. Büyük-küçük harf duyarsız arama yapan kodun bu ayrımı gözetmesi gerekir.

Beşinci ve altıncı satırlar sayısal sıralamayı gösterir: ölçüm kodları dizgi olarak
sıralandığında `T-10` `T-2` den önce gelir. Sayısal seçenek, dizgi içindeki sayı öbeklerini
sayı olarak karşılaştırır.

Son iki satır çoğul kurallarını gösterir. Arayüz metni yazılırken "1 ölçüm / 2 ölçüm"
ayrımının hangi kategorilere göre yapılacağı dile bağlıdır; kategori adı sorulur, karşılık
gelen metni uygulama sağlar. Türkçe ile İngilizcenin kategorileri bu örnekte örtüşür; başka
dillerde ikiden çok kategori bulunur ve iki dallı bir koşul yetmez.

#### Biçimleyicinin Maliyeti ve Kararlılığı

Biçimleyici nesnesinin **kurulması pahalıdır**; dil verisi çözümlenir ve seçenekler
uzlaştırılır. Biçimleme işleminin kendisi ise ucuzdur. Buradan tek bir kural çıkar:
biçimleyici bir kez kurulur ve yeniden kullanılır. Bin satırlık bir tabloyu çizerken her
hücrede yeni bir biçimleyici kuran kod, önceki dersin kare bütçesini tek başına tüketebilir.

İkinci kural çıktının kararlılığıyla ilgilidir. Üretilen metin **kullanıcıya gösterilmek
içindir**; çözümlenmek, karşılaştırılmak ya da sunucuya gönderilmek için değil. Dil verisi
sürümler arasında değişebilir; boşluk karakteri, ayraç ya da kısaltma başka bir biçim
alabilir. Veri her zaman ham hâliyle saklanır ve taşınır; biçimleme yalnızca sunum
katmanında yapılır. Sınamalarda da biçimlenmiş metnin tamamı bir sabitle karşılaştırılmaz;
parçalara ayırma işlemi bu yüzden vardır.

#### Özet

- Yerel ayar etiketi dil, yazı sistemi ve bölge alt etiketlerinden oluşur; bölge dili değil
  biçimleme kurallarını seçer ve tercih bir liste olarak verilir.
- Ondalık ayırıcı, binlik ayırıcı ve işaretin konumu dil verisinden gelir; sayıyı elle
  birleştiren kod bu bilgiyi kaybeder.
- Biçimlenmiş metin parçalara ayrılabilir; alanları ayrı biçemlemek için metni bölmek
  gerekmez.
- Ölçüm anı evrensel zamanda saklanır ve gösterim sırasında saat dilimi açıkça verilir;
  verilmediğinde cihazın dilimi kullanılır.
- Kod noktası sırası hiçbir abeceye karşılık gelmez; yerele duyarlı sıralama abeceyi izler
  ve Türkçede noktalı ile noktasız harf en gevşek duyarlılıkta bile ayrı harftir.
- Biçimleyici bir kez kurulup yeniden kullanılır; ürettiği metin sunum içindir, veri her
  zaman ham hâliyle saklanır ve çözümleme girdisi olarak kullanılmaz.

#### Sonraki Adım

Yerel ayar, saat dilimi ve dil tercihi kullanıcının cihazından **izin sorulmadan** okunur;
bunlar sayfanın zaten bildiği bilgilerdir. Ölçüm istasyonu sayfasının isteyeceği başka
şeyler de vardır: ölçümün alındığı noktanın koordinatları, bir arıza kaydına eklenecek
fotoğraf, sesli not. Bunların hiçbiri sorulmadan verilmez. Sonraki ders, izin gerektiren
yetenekleri, iznin durumlarını, kullanıcı hareketine bağlılığı ve isteğin reddedilmesi
durumunda sayfanın nasıl çalışmayı sürdüreceğini ele alır.

### Medya ve Aygıt API'leri

Source: https://academia.sh/tr/kurslar/tarayici-platformu/medya-ve-aygit-apileri

İzin gerektiren yeteneklere erişim; iznin durumları ve ön koşulları, güvenli bağlam ile kullanıcı hareketi gereği, yetenek saptama ile zarif düşüş, medya izlerinin yaşam döngüsü ve konum erişiminin gizlilik dengesi.

Yerel ayar, saat dilimi ve dil tercihi kullanıcının cihazından izin sorulmadan okunur;
bunlar sayfanın zaten bildiği bilgilerdir. Ölçüm istasyonu sayfasının isteyeceği başka
şeyler de vardır: ölçümün alındığı noktanın koordinatları, bir arıza kaydına eklenecek
fotoğraf, sesli not.

Bunların hiçbiri sorulmadan verilmez. Aralarındaki ortak nokta, kullanıcının cihazından
**dışarı bilgi taşımalarıdır**. Tarayıcı bu yetenekleri ortak bir izin düzeneğinin
arkasına koyar ve düzenek bir kez öğrenildiğinde hepsine uygulanır.

#### İzin Gerektiren Yetenekler

Yetenekler üç kümeye ayrılır.

**İzin gerektirmeyenler**, cihazın kullanıcıyı tanımlamayan genel özellikleridir: görünüm
alanı ölçüleri, dil tercihi, saat dilimi, bağlantının çevrimiçi olup olmadığı. Bunlar
sorulmadan okunur.

**Açık izin gerektirenler**, kullanıcının çevresinden veri toplayanlardır: kamera,
mikrofon, konum, ekran paylaşımı, hareket ve yönelim sensörleri, panonun okunması,
bildirim gönderme.

**Kullanıcı hareketiyle sınırlananlar**, izin sormayan ama kendiliğinden çalıştırılamayan
yeteneklerdir: tam ekrana geçmek, dosya seçtirmek, ekranı uyanık tutmak, panoya yazmak.
Bunlar yalnızca kullanıcının bir eyleminden doğan olay işlenirken çalışır.

Bir yetenek daha en baştan kapalı olabilir. Gömülü İçerik dersinde tanımlanan yetenek
izni bildirimi, bir çerçeveye hangi yeteneklerin devredildiğini belirler; devredilmemiş bir
yetenek, kullanıcı izin verse de çalışmaz.

#### İznin Durumları

İzin üç durumdan birinde bulunur: henüz sorulmamış, verilmiş, reddedilmiş. İstek yapıldığında
tarayıcı önce ön koşulları sınar, sonra duruma bakar.

```js
// izin.mjs — izin durumu makinesi ve istegin on kosullari
function istek(durum, ortam) {
  if (ortam.guvenliBaglam === false)
    return { durum, sonuc: "hata: guvenli baglam yok, sorulmadi" };
  if (durum === "verildi") return { durum, sonuc: "dogrudan verildi, sorulmadi" };
  if (durum === "reddedildi") return { durum, sonuc: "reddedildi, yeniden sorulmadi" };
  if (ortam.kullaniciHareketi === false)
    return { durum, sonuc: "hata: kullanici hareketi yok, sorulmadi" };
  return { durum: ortam.yanit, sonuc: `kullaniciya soruldu -> ${ortam.yanit}` };
}

const senaryolar = [
  ["guvensiz baglam", { guvenliBaglam: false, kullaniciHareketi: true, yanit: "verildi" }],
  ["hareketsiz cagri", { guvenliBaglam: true, kullaniciHareketi: false, yanit: "verildi" }],
  ["kullanici kapatti", { guvenliBaglam: true, kullaniciHareketi: true, yanit: "sorulmadi" }],
  ["kullanici verdi", { guvenliBaglam: true, kullaniciHareketi: true, yanit: "verildi" }],
  ["ikinci istek", { guvenliBaglam: true, kullaniciHareketi: true, yanit: "verildi" }],
];

let durum = "sorulmadi";
console.log("baslangic durumu:", durum);
for (const [ad, ortam] of senaryolar) {
  const sonuc = istek(durum, ortam);
  durum = sonuc.durum;
  console.log(ad.padEnd(18), "->", sonuc.sonuc.padEnd(38), "| durum:", durum);
}

// Kullanici ayarlardan izni geri aldiginda durum degisir; sayfa bunu yalniz sorarak ogrenir.
durum = "reddedildi";
console.log("izin geri alindi   ->", istek(durum, { guvenliBaglam: true, kullaniciHareketi: true, yanit: "verildi" }).sonuc);
```

```
baslangic durumu: sorulmadi
guvensiz baglam    -> hata: guvenli baglam yok, sorulmadi    | durum: sorulmadi
hareketsiz cagri   -> hata: kullanici hareketi yok, sorulmadi | durum: sorulmadi
kullanici kapatti  -> kullaniciya soruldu -> sorulmadi       | durum: sorulmadi
kullanici verdi    -> kullaniciya soruldu -> verildi         | durum: verildi
ikinci istek       -> dogrudan verildi, sorulmadi            | durum: verildi
izin geri alindi   -> reddedildi, yeniden sorulmadi
```

Beş kural bu çıktıda toplanır.

**Güvenli bağlam zorunludur.** Şifrelenmemiş bir bağlantı üzerinden sunulan sayfa bu
yetenekleri isteyemez; HTTPS'in Rolü dersinde tanımlanan gizlilik güvencesi olmadan
kameradan gelen veri ağda açık taşınır. Yerel geliştirme adresi bu kuralın tanımlı
istisnasıdır.

**Kullanıcı hareketi gerekir.** Ekran paylaşımı ve panoyu okuma, kullanıcı hareketiyle başlamayan
isteği doğrudan reddeder; kamera ve mikrofonda bu koşul şart değildir ama sayfa yüklenir yüklenmez
izin isteyen kod tarayıcı tarafından yine reddedilebilir. İstek, kullanıcının bir düğmeye
basmasıyla başlayan olay işlenirken yapılır.

**Kapatmak reddetmek değildir.** Kullanıcı soruyu yanıtlamadan kapattığında durum
"sorulmamış" olarak kalır; sonraki istek yeniden sorabilir. Bu, ısrarla sormanın da kapısını
açar ve kötüye kullanıldığında tarayıcı istekleri kendiliğinden engeller.

**Verilmiş izin yeniden sorulmaz.** İkinci istek kullanıcıyı rahatsız etmeden sonuçlanır.

**Reddedilmiş izin de yeniden sorulmaz.** İstek doğrudan başarısız olur; kullanıcıya
gösterilecek şey bir soru değil, izni tarayıcı ayarlarından nasıl açacağını anlatan bir
açıklamadır.

İzin durumu ayrıca **sorgulanabilir**: istekte bulunmadan durumun ne olduğu öğrenilebilir
ve durum değiştiğinde bildirim alınabilir. Bu, kullanıcı ayarlardan izni geri aldığında
arayüzü güncellemeyi sağlar.

#### Yetenek Saptama ve Zarif Düşüş

Bir yeteneğin bulunmaması ile reddedilmesi ayrı durumlardır ve ayrı ele alınır.

**Yetenek saptama (feature detection)**, ilgili arayüzün var olup olmadığını çalışma
zamanında sınamaktır. Tarayıcı adına ya da sürümüne bakan kod yanlış yoldur: adı sınamak
yeteneği sınamaz. Sınama, kullanılacak nesnenin ya da yöntemin varlığına bakarak yapılır ve
sonuç olumsuzsa arayüz o seçeneği hiç göstermez.

Bu üç durumun karşılıkları farklıdır. Yetenek yoksa seçenek gösterilmez. Yetenek var ama
izin reddedilmişse seçenek gösterilir ve nasıl açılacağı anlatılır. Yetenek var, izin var
ama işlem başarısız olduysa hata bildirilir ve yeniden denenebilir.

Her durumda ortak kural şudur: sayfa, yetenek olmadan da işini görmelidir. Konumu
okuyamıyorsa kullanıcıdan koordinatı yazmasını ister; kamerayı açamıyorsa dosya
seçtirir. Yeteneğe bağımlı tek yol bırakan tasarım, reddeden kullanıcıyı sayfanın dışında
bırakır.

#### Medya İzleri ve Aygıtın Bırakılması

Kamera ya da mikrofon isteği bir **akış** döndürür. Akış, her biri tek bir kaynağa bağlı
**izlerden (track)** oluşur ve kaynağın serbest bırakılması izlerin durdurulmasına bağlıdır.

```js
// akis.mjs — medya izlerinin yasam dongusu ve aygitin serbest birakilmasi
const aygitlar = { kamera: 0, mikrofon: 0 };   // kac iz aygiti kullaniyor

const iz = (tur, aygit, ayarlar) => {
  aygitlar[aygit] += 1;
  return {
    tur, aygit, ayarlar, canli: true,
    durdur() { if (this.canli) { this.canli = false; aygitlar[aygit] -= 1; } },
  };
};

function akisAl(kisit) {
  const izler = [];
  if (kisit.video) izler.push(iz("video", "kamera", { genislik: 1280, yukseklik: 720 }));
  if (kisit.ses) izler.push(iz("ses", "mikrofon", { yankiGiderme: true }));
  return { izler, durdur() { for (const i of this.izler) i.durdur(); } };
}

const yaz = (etiket) =>
  console.log(etiket.padEnd(24), "kamera:", aygitlar.kamera, "| mikrofon:", aygitlar.mikrofon);

yaz("baslangic");
const akis = akisAl({ video: true, ses: true });
yaz("akis alindi");
console.log("izler:", akis.izler.map((i) => `${i.tur}(${JSON.stringify(i.ayarlar)})`).join(" "));

akis.izler[0].durdur();                        // yalniz video izi durduruldu
yaz("video izi durduruldu");
akis.izler[0].durdur();                        // ikinci durdurma etkisizdir
yaz("ayni iz yeniden durduruldu");
akis.durdur();
yaz("akis durduruldu");
console.log("izlerin durumu:", akis.izler.map((i) => `${i.tur}=${i.canli ? "canli" : "bitti"}`).join(" "));
```

```
baslangic                kamera: 0 | mikrofon: 0
akis alindi              kamera: 1 | mikrofon: 1
izler: video({"genislik":1280,"yukseklik":720}) ses({"yankiGiderme":true})
video izi durduruldu     kamera: 0 | mikrofon: 1
ayni iz yeniden durduruldu kamera: 0 | mikrofon: 1
akis durduruldu          kamera: 0 | mikrofon: 0
izlerin durumu: video=bitti ses=bitti
```

Dördüncü satır en sık yapılan hatayı gösterir. Görüntü ögesinin kaynağını boşaltmak ya da
ögeyi ağaçtan çıkarmak izi durdurmaz; kayıt göstergesi yanmaya devam eder ve kullanıcı
sayfanın kendisini dinlediğini düşünür. Aygıt yalnızca **her izi tek tek durdurunca**
serbest kalır. Durdurma işlemi yinelemeye dayanıklıdır: ikinci çağrı sayacı bozmaz.

İstek sırasında verilen **kısıtlar**, istenen çözünürlük ve ses işleme gibi ayarları
bildirir. Bunlar bir dilektir, güvence değil: cihazın sağladığı gerçek ayarlar izden geri
okunur ve istenenden farklı olabilir. Kısıtın zorunlu biçimde verilmesi, karşılanamadığında
isteğin tümüyle başarısız olması anlamına gelir.

#### Konum ve Sensörler

Konum erişiminin iki biçimi vardır: tek seferlik okuma ve sürekli izleme. İzleme, konum
değiştikçe bildirim üretir ve açıkça bırakılana kadar sürer; bırakılmayan izleme pili
tüketir.

İstekte bildirilen **yüksek doğruluk** isteği, uydu tabanlı konumlandırmayı devreye
sokabilir; bu daha yavaş ve daha pahalıdır. Ölçüm noktasının hangi vadide olduğunu bilmek
için gerekli olan doğruluk ile kaldırımın hangi tarafında durulduğunu bilmek için gerekli
doğruluk aynı değildir; istenmeyen doğruluk istenmez.

Gelen konumun bir **doğruluk yarıçapı** vardır ve bu değer arayüze yansıtılmalıdır; tek bir
nokta göstermek, elde olmayan bir kesinlik iddia eder. Zaman aşımı ve önbellek yaşı da
istekte bildirilir: eski bir okuma kabul edilebilir mi, ne kadar beklenecek.

Hareket ve yönelim sensörleri benzer bir çerçevede durur ve bazı platformlarda ayrıca izin
ister. Ölçüm alırken cihazın eğimini kaydetmek gibi bir kullanım, sensör verisinin
kullanıcıyı tanımlamaya da yarayabileceği gerçeğiyle birlikte değerlendirilir.

#### Gizlilik Sözleşmesi

Bu yeteneklerin ortak sözleşmesi dört maddededir. İzin **bağlam içinde**
istenir: kullanıcı neden sorulduğunu anladığı anda, yani ilgili düğmeye
bastığında. Toplanan veri **en azdır**: ölçümün yanına konum eklenecekse tam
koordinat yerine istasyon adı yeterli olabilir. Erişim **görünürdür**:
yetenek kullanılırken arayüz bunu gösterir. Ve erişim **bırakılır**: iş
bittiğinde izler durdurulur ve izlemeler sonlandırılır.

#### Özet

- Yetenekler izin gerektirmeyen, açık izin gerektiren ve kullanıcı hareketiyle sınırlanan
  kümelere ayrılır; çerçevelere devredilmemiş bir yetenek izinle de açılmaz.
- İzin isteği güvenli bağlam ve kullanıcı hareketi ön koşullarına bağlıdır; verilmiş ve
  reddedilmiş izinler yeniden sorulmaz, kapatılan soru ise durumu değiştirmez.
- Yetenek saptama tarayıcı adına değil arayüzün varlığına bakar; yetenek yokluğu, izin
  reddi ve işlem hatası ayrı karşılıklar gerektirir.
- Sayfa yetenek olmadan da işini görmelidir; yeteneğe bağımlı tek yol bırakan tasarım
  reddeden kullanıcıyı dışarıda bırakır.
- Aygıt yalnızca akıştaki her iz durdurulduğunda serbest kalır; ögeyi ağaçtan çıkarmak izi
  durdurmaz.
- Konumda doğruluk isteği maliyet üretir ve gelen okumanın doğruluk yarıçapı arayüze
  yansıtılır; izleme açıkça sonlandırılır.

#### Sonraki Adım

Bu dersteki yeteneklerin çoğu ağır veri üretir: kameradan gelen kareler, sensör okumaları,
seçilen bir ölçüm dosyasının binlerce satırı. Bunların işlenmesi ana iş parçacığında
yapıldığında Zamanlama ve Çizim Döngüsü dersindeki kare bütçesi tek bir çağrıda tükenir ve
arayüz yanıt vermeyi keser. İşi parçalara bölmek gecikmeyi dağıtır ama toplam süreyi
kısaltmaz. Sonraki ders, hesabı ana iş parçacığının dışına taşıyan çalışan iş parçacıklarını,
aralarındaki ileti geçişini ve bu ayrımın bedelini ele alır.

### Web İşçileri

Source: https://academia.sh/tr/kurslar/tarayici-platformu/web-iscileri

Hesabın ana iş parçacığı dışına taşınması; paylaşılmayan bellek modeli, ileti geçişi, yapılandırılmış kopyalamanın kapsamı ve maliyeti, aktarılabilir nesneler, işçi türleri ve ayrımın ne zaman kazanç sağladığı.

Önceki dersteki yeteneklerin çoğu ağır veri üretir: kameradan gelen kareler, sensör
okumaları, seçilen bir ölçüm dosyasının binlerce satırı. Bunların işlenmesi ana iş
parçacığında yapıldığında kare bütçesi tek bir çağrıda tükenir ve arayüz yanıt vermeyi
keser.

İşi parçalara bölmek gecikmeyi dağıtır ama toplam süreyi kısaltmaz; hesap yine aynı iş
parçacığında yapılır. İkinci çözüm, hesabı oradan tümüyle çıkarmaktır. Tarayıcı bunun için
ayrı iş parçacıklarında çalışan bir yürütme ortamı sunar.

#### Paylaşılmayan Bellek

**Çalışan (worker)**, ayrı bir iş parçacığında yürüyen ve sayfanın bir kopyası olmayan bir
yürütme ortamıdır. Kendi genel nesnesi, kendi öbeği ve kendi olay döngüsü vardır. Bundan üç
sonuç çıkar.

**Belge ağacına erişemez.** Düğüm seçmek, biçem okumak, öge eklemek çalışanın işi değildir.
Arayüzü güncelleyen tek yer ana iş parçacığıdır; çalışan yalnızca veri üretir.

**Değişken paylaşmaz.** Ana iş parçacığındaki bir nesne çalışanda görünmez. Aralarındaki
tek bağ **ileti geçişidir (message passing)**. Bu, Eşzamansız JavaScript ve Çalışma Zamanı
kursundaki tek iş parçacıklı modelin bozulmadığı anlamına gelir: iki taraf da kendi içinde
tek iş parçacıklıdır, aralarında ortak durum yoktur ve klasik yarış koşulları oluşmaz.

**Ayrı bir kaynak tüketir.** Her çalışan kendi belleğini ve kendi çalışma zamanı yapısını
taşır; sayısı sınırsız artırılmaz.

#### Bir Çalışanı Çalıştırmak

Aşağıdaki örnek `node:worker_threads` ile çalışır ve gerçek bir iş parçacığı açar.
Tarayıcıdaki karşılığı aynı modeli izler: çalışan bir betik adresiyle kurulur, iki taraf
ileti gönderip alır. Adlandırma ayrıntıları ayrılır — tarayıcı tarafında başlangıç verisi
ayrı bir iletiyle gönderilir — ancak akış aynıdır.

```js
// isci.mjs — hesabin ayri bir is parcacigina tasinmasi (node:worker_threads)
import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads";

// Ayni dosya hem ana is parcaciginda hem calisanda calisir; dal isMainThread ile ayrilir.
function ozet(olcumler) {
  let toplam = 0;
  let enBuyuk = -Infinity;
  let esikUstu = 0;
  for (const d of olcumler) {
    toplam += d;
    if (d > enBuyuk) enBuyuk = d;
    if (d > 5) esikUstu += 1;
  }
  return { sayi: olcumler.length, ortalama: toplam / olcumler.length, enBuyuk, esikUstu };
}

if (isMainThread) {
  // Belirlenimci veri: her calistirmada ayni dizi uretilir.
  const olcumler = Array.from({ length: 200000 }, (_, i) => Math.sin(i / 7) * 10);
  const calisan = new Worker(new URL(import.meta.url), { workerData: olcumler });

  calisan.on("message", (sonuc) => {
    console.log("sonuc  :", {
      sayi: sonuc.sayi,
      ortalama: Number(sonuc.ortalama.toFixed(6)),
      enBuyuk: Number(sonuc.enBuyuk.toFixed(6)),
      esikUstu: sonuc.esikUstu,
    });
  });
  calisan.on("error", (hata) => console.log("hata   :", hata.message));
  calisan.on("exit", (kod) => console.log("cikis  :", kod));
  console.log("ana is parcacigi calisani kurdu ve beklemeden devam etti");
} else {
  parentPort.postMessage(ozet(workerData));
}
```

```
ana is parcacigi calisani kurdu ve beklemeden devam etti
sonuc  : { sayi: 200000, ortalama: 0.000399, enBuyuk: 10, esikUstu: 66672 }
cikis  : 0
```

Çıktının sırası modelin özetidir. Ana iş parçacığı çalışanı kurar ve **beklemeden** devam
eder; sonuç geldiğinde geri çağırım işler. İki yüz bin öge üzerindeki döngü boyunca ana iş
parçacığı boştur ve olayları işlemeyi sürdürür.

Sonuçtaki en büyük değer, örnek verinin üretiminden gelir: sinüs işlevinin en büyük değeri
bire yaklaşır, on ile çarpıldığında ondur. Eşik üstü sayı da veriden türer; veri
belirlenimci olduğu için her çalıştırmada aynı çıkar. Çıkış kodunun sıfır olması çalışanın
hatasız kapandığını bildirir.

#### İleti Geçişinin Bedeli

İleti geçişi ücretsiz değildir. Gönderilen değer paylaşılmaz, **kopyalanır**. Kopyalama
kuralı JSON'unkinden geniştir ve ayrı bir adı vardır.

```js
// kopya.mjs — yapilandirilmis kopyalama ve aktarilabilir nesne
const kayit = {
  kod: "T-01",
  an: new Date("2024-02-11T06:00:00Z"),
  etiketler: new Map([["gece", true]]),
  ornek: new Uint8Array([3, 1, 4]),
};
const kopya = structuredClone(kayit);
console.log("tarih korundu :", kopya.an instanceof Date, "| eslem korundu:", kopya.etiketler instanceof Map);
console.log("dizi korundu  :", kopya.ornek instanceof Uint8Array, "| ayni nesne mi:", kopya.ornek === kayit.ornek);

// JSON'un yapamadigi: dongusel basvuru
const dugum = { ad: "istasyon" };
dugum.kendisi = dugum;
const dugumKopya = structuredClone(dugum);
console.log("dongu korundu :", dugumKopya.kendisi === dugumKopya);

// Kopyalanamayan degerler hata uretir.
for (const [ad, deger] of [["islev", { f: () => 1 }], ["simge", { s: Symbol("x") }]]) {
  try {
    structuredClone(deger);
    console.log(ad.padEnd(14), ": kopyalandi");
  } catch (hata) {
    console.log(ad.padEnd(14), ":", hata.name);
  }
}

// Aktarim: arabellek kopyalanmaz, sahipligi devredilir.
const arabellek = new ArrayBuffer(1024);
const aktarilan = structuredClone(arabellek, { transfer: [arabellek] });
console.log("aktarim sonrasi kaynak:", arabellek.byteLength, "| hedef:", aktarilan.byteLength);
```

```
tarih korundu : true | eslem korundu: true
dizi korundu  : true | ayni nesne mi: false
dongu korundu : true
islev          : DataCloneError
simge          : DataCloneError
aktarim sonrasi kaynak: 0 | hedef: 1024
```

**Yapılandırılmış kopyalama (structured clone)**, Depolama API'leri dersinde görülen JSON
kayıplarını yaşamaz: tarih tarih kalır, eşlem eşlem kalır, döngüsel başvuru korunur. Buna
karşılık kopya gerçekten kopyadır; iki taraftaki nesneler ayrı nesnelerdir ve birinde
yapılan değişiklik diğerine yansımaz.

**Kopyalanamayan değerler vardır.** İşlev ve simge kopyalanamaz ve hata üretir. Aynı kural
düğümler için de geçerlidir; bir ögeyi çalışana göndermek mümkün değildir. Bu, belge
ağacına erişememenin ileti tarafındaki karşılığıdır.

**Aktarılabilir nesneler (transferable objects)** kopyalama maliyetinden kaçmayı sağlar.
Aktarım listesine konan bir arabellek kopyalanmaz; sahipliği karşı tarafa geçer ve
gönderende kullanılamaz hâle gelir — çıktıda kaynağın uzunluğu sıfırdır. Büyük ikili veri
bu yolla neredeyse bedelsiz taşınır, ancak gönderen tarafın onu bir daha kullanmayacağı
kesin olmalıdır.

Bir de gerçekten **paylaşılan bellek** vardır: iki iş parçacığının aynı arabelleği görmesi.
Bu yol, sayfanın diğer kaynaklardan yalıtılmış olmasını gerektiren koşullara bağlıdır ve
paylaşılan belleğe erişim, işlemleri bölünmez kılan bir düzenekle sıraya konmalıdır. Yarış
koşullarını geri getirdiği için en son başvurulan yoldur.

#### Türler ve Yaşam Süresi

Çalışan iş parçacıklarının üç türü vardır. **Adanmış çalışan (dedicated worker)** onu kuran
belgeye aittir; belge kapandığında sonlanır. **Paylaşılan çalışan (shared worker)** aynı
kaynağın birden çok belgesine hizmet eder ve bağlantı noktaları üzerinden konuşur; sekmeler
arasında ortak bir hesabı bir kez yapmaya yarar. **Servis çalışanı (service worker)** ise
ayrı bir amaca hizmet eder — ağ isteklerine aracılık eder ve belge kapalıyken de
çalışabilir; Servis Çalışanları dersinin konusudur.

Çalışan açıkça sonlandırılabilir. Sonlandırma kesindir: çalışan iş yarıda kalır. Kullanıcı
başka bir görünüme geçtiğinde sürmekte olan bir hesabın sonlandırılması, önceki derslerdeki
iptal kuralının bu bağlamdaki karşılığıdır. Sonlandırılmayan çalışan bellekte kalır ve
kaynak tüketmeyi sürdürür.

Hata yönetimi de ayrıdır. Çalışanın içinde yakalanmayan bir hata ana iş parçacığına bir
olayla bildirilir; çalışanın çökmesi sayfayı çökertmez. Bu yalıtım bir kazançtır: güvenilmez
bir hesap ana iş parçacığından ayrı tutulur.

#### Ne Zaman Değer

Ayrımın bir kurulum maliyeti vardır: iş parçacığının açılması, betiğin yüklenmesi, verinin
kopyalanması, sonucun geri kopyalanması. Küçük bir iş için bu maliyet, kazancından
büyüktür.

Ölçüt şudur: iş **saf hesap** mı, **uzun** mu, **belge ağacından bağımsız** mı. Üç koşul
birden sağlanıyorsa çalışan kazandırır — büyük bir ölçüm dosyasının çözümlenmesi,
görüntü işleme, sıkıştırma, arama dizini kurma. Koşullardan biri düşerse önceki dersteki
bölme yöntemi daha uygundur.

Veri aktarımının kendisi de ölçüte girer. Kopyalanacak veri hesaptan pahalıysa kazanç
kaybolur; bu durumda ham veriyi çalışana bir kez gönderip ardından yalnızca küçük
iletilerle çalışmak ya da aktarılabilir nesne kullanmak gerekir.

#### Özet

- Çalışan iş parçacığı kendi belleğine ve olay döngüsüne sahiptir; belge ağacına erişemez
  ve ana iş parçacığıyla yalnızca ileti geçişi üzerinden konuşur.
- Ana iş parçacığı çalışanı kurduktan sonra beklemez; sonuç geldiğinde geri çağırım işler.
- İleti geçişinde değer yapılandırılmış kopyalamayla taşınır; JSON'un kaybettiği tipler ve
  döngüsel başvurular korunur, işlev ve simge gibi değerler hata üretir.
- Aktarılabilir nesnelerde kopyalama yapılmaz, sahiplik devredilir ve gönderen tarafta
  kullanılamaz hâle gelir.
- Adanmış çalışan belgeye, paylaşılan çalışan aynı kaynağın belgelerine hizmet eder; servis
  çalışanı ağ aracılığı için ayrı bir tür oluşturur.
- Çalışan, iş saf hesapsa, uzunsa ve belge ağacından bağımsızsa kazandırır; kurulum ve
  kopyalama maliyeti bu koşullar sağlanmadığında kazancı yutar.

#### Sonraki Adım

Bu konu boyunca sayfaya davranış eklendi: olaylar devredildi, varsayılan davranışlara
müdahale edildi, durum depolandı ve adrese yazıldı, ölçümler gözlendi, hesap ana iş
parçacığından çıkarıldı. Her yetenek ayrı ayrı kullanılabilir durumda; ancak hepsi hâlâ
sayfanın genel kodunda dağınık duruyor. Ölçüm satırının kendi yapısı, kendi biçemi ve kendi
davranışı bir arada değil.

Yükü dağıtmanın bir sonraki adımı, arayüz parçasının kendini toparlamasıdır: bir ögenin
kendi etiketini, kendi yaşam döngüsünü ve kendi davranışını tarayıcının tanıdığı biçimde
tanımlaması. Sonraki konu bu paketlemeyle başlar; ilk dersi olan Özel Elemanlar, kendi
adına sahip bir ögenin tarayıcıya nasıl tanıtıldığını ve ağaca girip çıkarken hangi kancaların
çağrıldığını ele alır.

## Web Bileşenleri ve Çevrimdışı

Özel ögelerin yaşam döngüsü, gölge ağacın kapsüllemesi, şablon ve yuvalar, isteklerin araya girilerek yönetilmesi ve önbellek tazelik dengesi.

### Özel Elemanlar

Source: https://academia.sh/tr/kurslar/tarayici-platformu/ozel-elemanlar

Tarayıcının öge kaydına yeni bir etiket adı tanıtmak; ad kuralı, yaşam döngüsü geri çağrılarının sırası, tanımdan önce ağaca girmiş ögenin yükseltilmesi ve öznitelik–özellik yansıması.

Önceki ders ana iş parçacığındaki yükü çalışan iş parçacıklarına dağıttı: hesap bir
yerde, arayüz başka bir yerde. Yük dağıtıldıktan sonra geriye arayüzün kendi sorunu
kalır. İstasyon sayfasındaki ölçüm rozeti — değeri, birimi ve eşik rengiyle bir sayıyı
gösteren küçük parça — sayfada beş kez, gösterge panosunda on beş kez tekrarlanır. Her
tekrarda aynı işaretleme elle yazılır, aynı sınıflar elle verilir, aynı dinleyiciler elle
bağlanır.

Bu tekrarı bir yere toplamanın yolu, tarayıcıya yeni bir etiket adı tanıtmaktır: yapısı,
davranışı ve yaşam döngüsü kendi içinde tanımlı, belgede tek satırla yazılan bir öge. Bu
derste kurulan mekanizmanın adı **özel öge (custom element)** dir.

#### Ad Kuralı ve Öge Kaydı

Tarayıcı, etiket adlarını bir tabloda tutar: `p`, `section`, `input` ve geri kalanı. Bu
tabloya program içinden yeni bir ad eklenir; ada karşılık gelen bir sınıf verilir ve
tarayıcı o addaki her ögeyi bu sınıfın bir örneğiyle ilişkilendirir. Bu tabloya **öge
kaydı (custom element registry)** denir.

Eklenen adın tek bir biçim kuralı vardır: **en az bir tire içermeli** ve küçük harfle
başlamalıdır. `olcum-rozeti` geçerlidir, `rozet` değildir. Kuralın gerekçesi kısıt değil
korumadır. HTML'e sonradan eklenen bir etiket adı hiçbir zaman tire içermez; tire içeren
ad uzayı böylece belge yazarına ayrılmış olur. Bu ayrım olmasaydı, bugün tanımlanan bir
`rozet` ögesi ileride standarda giren bir `rozet` etiketiyle çakışır ve sayfa sessizce
başka bir şey yapmaya başlardı.

Bir ad yalnızca bir kez tanımlanabilir. Aynı adı ikinci kez tanımlamaya çalışmak hata
üretir; bu, aynı bileşenin iki ayrı sürümünün aynı sayfaya girmesini erken yakalayan bir
denetimdir.

İki tür özel öge vardır. Birincisi, temel öge sınıfını genişleten ve kendi etiket adıyla
yazılan bağımsız ögedir. İkincisi, var olan bir etiketi genişletir ve o etiketin
davranışını devralır; belgede `is` özniteliğiyle yazılır. İkinci türün desteği koşulludur
ve özellik saptamayla sınanır; bu ders birinci türü esas alır.

#### Yaşam Döngüsü Geri Çağrıları

Özel ögenin sınıfı dört noktada tarayıcı tarafından çağrılır. Bunlara **yaşam döngüsü
geri çağrısı (lifecycle callback)** denir.

**Kurucu**, öge nesnesi ilk kez var edildiğinde çalışır. **Bağlanma geri çağrısı**, öge
belgeye bağlı bir ağaca girdiğinde çalışır. **Bağlantı kesilme geri çağrısı**, öge o
ağaçtan çıkarıldığında. **Öznitelik değişimi geri çağrısı**, önceden bildirilmiş bir
özniteliğin değeri değiştiğinde.

Son geri çağrının bir koşulu vardır: sınıf, hangi özniteliklerin izleneceğini durağan bir
listeyle bildirmelidir. Listede olmayan bir özniteliğin değişmesi hiçbir çağrı üretmez.
Bu, her öznitelik yazımının bir geri çağrı tetiklemesini önleyen bilinçli bir
sınırlamadır.

Sıra, elle yazılmış bir kurulum kodundan farklıdır ve tahmin edilerek öğrenilemez.
Aşağıdaki model çağrı sırasını görünür kılar.

```js
// yasam-dongusu.mjs — ozel oge yasam dongusunun ve yukseltmenin kucuk bir modeli
const cagrilar = [];

const belge = { ad: "#belge", ozn: {}, cocuklar: [], ebeveyn: null, ornek: null };
const bagliMi = (d) => { let a = d; while (a.ebeveyn) a = a.ebeveyn; return a === belge; };
function* hepsi(kok) { yield kok; for (const c of kok.cocuklar) yield* hepsi(c); }

const tanimlar = new Map();

// Yukseltme sirasi sabittir: kurucu → izlenen oznitelikler → bagliysa baglanma.
function yukselt(dugum) {
  const sinif = tanimlar.get(dugum.ad);
  if (!sinif || dugum.ornek) return;
  dugum.ornek = new sinif(dugum);
  for (const ad of sinif.izlenenOznitelikler ?? [])
    if (ad in dugum.ozn) dugum.ornek.oznitelikDegisti(ad, null, dugum.ozn[ad]);
  if (bagliMi(dugum)) dugum.ornek.baglandi();
}

// Oge olusturulurken tanim varsa yukseltme aninda olur, yoksa oge tanimsiz kalir.
function oge(ad, ozn = {}) {
  const dugum = { ad, ozn, cocuklar: [], ebeveyn: null, ornek: null };
  yukselt(dugum);
  return dugum;
}

function tanimla(ad, sinif) {
  if (!ad.includes("-")) throw new Error(`gecersiz ad: ${ad}`);
  if (tanimlar.has(ad)) throw new Error(`zaten tanimli: ${ad}`);
  tanimlar.set(ad, sinif);
  for (const d of hepsi(belge)) if (d.ad === ad) yukselt(d); // belge sirasina gore
}

function cikar(dugum) {
  const kardesler = dugum.ebeveyn.cocuklar;
  const bagliydi = bagliMi(dugum);
  kardesler.splice(kardesler.indexOf(dugum), 1);
  dugum.ebeveyn = null;
  if (bagliydi) dugum.ornek?.baglantiKesildi();
}

function ekle(ebeveyn, cocuk) {
  if (cocuk.ebeveyn) cikar(cocuk);
  cocuk.ebeveyn = ebeveyn;
  ebeveyn.cocuklar.push(cocuk);
  if (!cocuk.ornek) yukselt(cocuk);
  else if (bagliMi(cocuk)) cocuk.ornek.baglandi();
}

function oznitelikYaz(dugum, ad, deger) {
  const eski = dugum.ozn[ad] ?? null;
  dugum.ozn[ad] = deger;
  const izlenen = tanimlar.get(dugum.ad)?.izlenenOznitelikler ?? [];
  if (dugum.ornek && izlenen.includes(ad)) dugum.ornek.oznitelikDegisti(ad, eski, deger);
}

let sayac = 0;
class OlcumRozeti {
  static izlenenOznitelikler = ["deger", "birim"];
  constructor(dugum) {
    this.etiket = `rozet#${++sayac}`;
    cagrilar.push(`${this.etiket} kurucu`);
  }
  oznitelikDegisti(ad, eski, yeni) {
    cagrilar.push(`${this.etiket} oznitelikDegisti(${ad}, ${eski}, ${yeni})`);
  }
  baglandi() { cagrilar.push(`${this.etiket} baglandi`); }
  baglantiKesildi() { cagrilar.push(`${this.etiket} baglantiKesildi`); }
}

// A. Tanimdan ONCE agaca girmis iki oge (ayristiricinin urettigi durum).
const pano = oge("section", { id: "pano" });
ekle(belge, pano);
ekle(pano, oge("olcum-rozeti", { deger: "-4.2", birim: "C" }));
ekle(pano, oge("olcum-rozeti", { deger: "72" }));
cagrilar.push("--- tanimla('olcum-rozeti') ---");
tanimla("olcum-rozeti", OlcumRozeti);

// B. Tanimdan SONRA olusturulan bir oge.
cagrilar.push("--- tanimdan sonra olusturulan oge ---");
const yeni = oge("olcum-rozeti");
oznitelikYaz(yeni, "deger", "3.4");
ekle(pano, yeni);

// C. Bagli bir oge baska bir ebeveyne tasiniyor.
cagrilar.push("--- oge baska bir ebeveyne tasiniyor ---");
const yanPano = oge("aside", { id: "yan" });
ekle(belge, yanPano);
ekle(yanPano, yeni);

// D. Tire icermeyen ad.
cagrilar.push("--- tire icermeyen ad ---");
try { tanimla("rozet", OlcumRozeti); } catch (h) { cagrilar.push(h.message); }

console.log(cagrilar.join("\n"));
```

```
--- tanimla('olcum-rozeti') ---
rozet#1 kurucu
rozet#1 oznitelikDegisti(deger, null, -4.2)
rozet#1 oznitelikDegisti(birim, null, C)
rozet#1 baglandi
rozet#2 kurucu
rozet#2 oznitelikDegisti(deger, null, 72)
rozet#2 baglandi
--- tanimdan sonra olusturulan oge ---
rozet#3 kurucu
rozet#3 oznitelikDegisti(deger, null, 3.4)
rozet#3 baglandi
--- oge baska bir ebeveyne tasiniyor ---
rozet#3 baglantiKesildi
rozet#3 baglandi
--- tire icermeyen ad ---
gecersiz ad: rozet
```

Çıktının okunması gereken dört yeri var.

Birincisi, ilk yedi satır **tanım çağrılana kadar hiçbir geri çağrının çalışmadığını**
gösterir. Ögeler ağaçtaydı, öznitelikleri yerindeydi; bekleyen şey tanımdı.

İkincisi, her öge için sıra aynıdır: kurucu, sonra izlenen özniteliklerin her biri için
bir öznitelik değişimi çağrısı, sonra bağlanma. Bağlanma her zaman sonuncudur. Bir
bileşenin ilk çizimini bağlanma geri çağrısında yapması bu yüzden güvenlidir: o noktada
öznitelik değerleri okunmuştur.

Üçüncüsü, ikinci rozette `birim` özniteliği belgede yazılı olmadığı için çağrı üretmemiş,
eksik öznitelik için varsayılan bir çağrı yapılmamıştır. Varsayılan değeri kurmak sınıfın
işidir.

Dördüncüsü, taşınan öge için **bağlantı kesilme ve bağlanma art arda çalışmıştır**.
Ağaçta yer değiştirmek, ögeyi çıkarıp yeniden eklemektir. Buradan bağlayıcı bir kural
çıkar: bağlanma geri çağrısı ögenin ömrü boyunca birden çok kez çalışabilir, bu yüzden
yinelemeye dayanıklı yazılmalıdır. Dinleyici bağlayan bir bağlanma geri çağrısının
karşılığında bağlantı kesilme geri çağrısında dinleyiciyi kaldırması gerekir; yoksa her
taşımada bir dinleyici birikir.

#### Kurucuda Yapılamayanlar

Kurucu, ögenin en erken uğrağıdır ve bu erkenlik bir kısıt getirir. Kurucu çalıştığı anda
öge henüz ağaçta olmayabilir, öznitelikleri okunmamış olabilir, çocukları ayrıştırılmamış
olabilir. Ayrıştırıcı bir etiketi gördüğü anda örneği kurar; kapanış etiketine daha
gelmemiştir.

Bundan üç kural çıkar. Kurucu **öznitelik okumaz ve yazmaz**. Kurucu **çocuk düğüm
eklemez ve okumaz**. Kurucu **belgeye erişmez**. Kurucunun işi yalnızca ögenin kendi
alanlarını ilk değerlerine kurmaktır; belgeyle ilgili her şey bağlanma geri çağrısına
kalır.

Ayrıştırma sırasının bir yan sonucu daha vardır: belgenin ortasındaki bir özel ögenin
bağlanma geri çağrısı çalıştığında, o ögenin çocukları henüz ayrıştırılmamış olabilir.
Çocuklarını okuması gereken bir bileşen bunu ya ayrıştırmanın bitmesini bekleyerek ya da
çocuk listesini gözlemleyerek yapar; bağlanma anında sayarak yapamaz.

#### Yükseltme ve Tanımın Gecikmesi

Tanımdan önce ağaca girmiş bir ögenin tanım geldiğinde sınıfla ilişkilendirilmesine
**yükseltme (upgrade)** denir. Yükseltme yeni bir nesne üretmez: ağaçtaki nesne olduğu
gibi kalır, sınıfın davranışını kazanır.

Bu, sayfanın tanımdan önceki hâlinin bir sorun değil bir ara durum olduğu anlamına gelir.
Tanımı taşıyan betik ertelenmiş olarak yüklendiğinde, kullanıcı belgedeki ham ögeleri
kısa bir süre görür. Bu ara durum biçemle yönetilir: tanımlanmamış ögeleri seçen bir
sözde sınıf vardır ve bu ögeler tanım gelene kadar gizlenebilir ya da yer tutucu
biçimiyle gösterilebilir. Kurulacak denge, boş bir alan göstermekle biçimsiz içerik
göstermek arasındadır; ölçüm rozetinde doğru seçim, sayıyı ham hâliyle göstermek ve
yalnızca eşik rengini tanım gelene kadar uygulamamaktır — okunabilir içerik hiç
gizlenmez.

Program tarafında da bir bekleme noktası gerekir: bir adın tanımlanmasını bekleyen ve
tanım geldiğinde çözülen bir söz. Bir ögenin özelliklerine yükseltme öncesinde erişen kod
bu sözü bekler; beklemeyen kod bir sonraki bölümdeki tuzağa düşer.

#### Öznitelik ile Özellik Arasında Yansıma

DOM API'si dersinde kurulan ayrım burada tasarım kararına dönüşür: öznitelik belgede
yazılı dizgidir, özellik program tarafındaki yaşayan değerdir. Bir özel öge ikisini de
sunar ve arada bir **yansıma (reflection)** kurar: özelliğe yazılan değer özniteliğe
yazılır, özniteliğe yazılan değer özelliği günceller.

İki yönlü bu bağ dikkatsiz kurulduğunda kendi kendini besler. Özellik ayarlayıcısı
özniteliği yazar, öznitelik değişimi geri çağrısı özelliği yazar, o da özniteliği yeniden
yazar. Döngüyü kesen kural sadedir: **her iki yönde de yazmadan önce değerin gerçekten
değiştiği sınanır.** Aynı değeri yeniden yazan bir adım zincirin devamını kesmelidir.

İkinci tuzak daha sinsidir ve yükseltmenin gecikmesinden doğar.

```js
// yukseltme-tuzagi.mjs — tanimdan once atanan ozellik erisimciyi golgeler
// Yukseltme, ogenin prototipini sinif prototipiyle degistirmektir; oge nesnesi aynidir.
class Rozet {
  get deger() { return this.degerAlani ?? null; }
  set deger(v) {
    this.degerAlani = Number(v);
    this.cizimSayisi = (this.cizimSayisi ?? 0) + 1; // gercek bir ogede bu, yeniden cizim olurdu
  }
}

function yukselt(dugum, { kurtar }) {
  Object.setPrototypeOf(dugum, Rozet.prototype);
  if (kurtar && Object.hasOwn(dugum, "deger")) {
    const bekleyen = dugum.deger; // erisimciyi golgeleyen kendi ozelligi
    delete dugum.deger;           // golge kaldirilir
    dugum.deger = bekleyen;       // atama artik erisimciye gider
  }
}

console.log("cagiran kod, tanim yuklenmeden once ozellige deger atiyor:");
for (const kurtar of [false, true]) {
  const dugum = {};   // tanimi henuz yuklenmemis oge
  dugum.deger = "5";  // erisimci yok: ogenin kendi ozelligi olusur
  yukselt(dugum, { kurtar });
  console.log(
    `  kurtarma ${kurtar ? "var" : "yok"} → deger: ${JSON.stringify(dugum.deger)},`,
    `tip: ${typeof dugum.deger}, cizim: ${dugum.cizimSayisi ?? 0}`,
  );
}
```

```
cagiran kod, tanim yuklenmeden once ozellige deger atiyor:
  kurtarma yok → deger: "5", tip: string, cizim: 0
  kurtarma var → deger: 5, tip: number, cizim: 1
```

Tanım yüklenmeden önce özelliğe yapılan atama, ögenin **kendi özelliğini** oluşturur.
Yükseltme prototipi değiştirir, ama prototipteki erişimci ögenin kendi özelliğinin
altında kalır ve hiçbir zaman çağrılmaz. İlk satırda değer dizgi olarak durur, dönüşüm
yapılmamış, çizim tetiklenmemiştir. Sorun sessizdir: bir hata iletisi yoktur, yalnızca
bileşen çalışmaz.

Kurtarma tek kalıptır ve kurucuda uygulanır: sınıfın tanımladığı her özellik için, ögede
aynı adda bir kendi özelliği varsa değeri alınır, özellik silinir ve yeniden atanır.
Silme prototipteki erişimciyi görünür kılar, yeniden atama onu çalıştırır. İkinci satırda
değer sayıya dönüşmüş ve çizim bir kez tetiklenmiştir.

#### Özet

- Özel öge, tarayıcının öge kaydına bir etiket adı ile bir sınıf çifti eklemektir; ad en
  az bir tire içermeli ve yalnızca bir kez tanımlanmalıdır.
- Tire koşulu, sonradan standarda girecek etiket adlarıyla çakışmayı önlemek için ad
  uzayını ayırır.
- Yaşam döngüsü çağrıları sabit sırayla işler: kurucu, izlenen özniteliklerin değişim
  çağrıları, sonra bağlanma; bağlantı kesilme ise ağaçtan çıkarılışta.
- Bağlanma geri çağrısı ögenin ömrü boyunca birden çok kez çalışabilir; kurduğu her şeyi
  bağlantı kesilme geri çağrısı sökmelidir.
- Kurucu öznitelik okumaz, çocuk eklemez, belgeye erişmez; belgeyle ilgili iş bağlanma
  geri çağrısına aittir.
- Tanımdan önce ağaçta bulunan ögeler yükseltilir; tanımdan önce özelliğe yazılan değer
  erişimciyi gölgeler ve kurucuda sil–yeniden ata kalıbıyla kurtarılır.

#### Sonraki Adım

Bu ders ögeye bir ad ve bir yaşam döngüsü verdi, ama içini açık bıraktı. Rozetin işaretlemesi
hâlâ belgenin ortak ağacında durur: sayfanın herhangi bir yerinde yazılmış bir biçem kuralı
rozetin içine sızabilir, rozetin kendi kuralı da dışarı taşabilir; sayfadaki bir seçici
rozetin iç düğümlerini bulur ve onlara dokunur. Yeniden kullanılabilir bir parçanın
gereksinimi tam da bunun tersidir: içerisi dışarıdan görünmesin, dışarısı içeriden
etkilenmesin. Sonraki ders belge ağacının içine ayrı bir ağaç açan ve bu iki yönlü yalıtımı
kuran mekanizmayı inceler.

### Gölge DOM

Source: https://academia.sh/tr/kurslar/tarayici-platformu/golge-dom

Bir ögenin içine açılan ayrı belge ağacı; seçicinin durduğu sınır, biçemin iki yönlü yalıtımı, kalıtımın ve özel özelliklerin sınırdan geçişi, açık ile kapalı kip ve olay hedefinin çevrilmesi.

Önceki ders ögeye bir ad ve bir yaşam döngüsü verdi; içini açık bıraktı. Ölçüm rozetinin
işaretlemesi hâlâ belgenin ortak ağacındadır. Sayfanın herhangi bir yerinde yazılmış
`.deger { font-weight: 700 }` kuralı rozetin içindeki değeri de yakalar; rozetin kendi
kuralı sayfadaki başka bir `.deger` ögesine sızar; belgeye bağlanmış bir seçici rozetin iç
düğümlerini bulur ve onlara dokunur.

Yeniden kullanılabilir bir parçanın gereksinimi bunun tersidir. Bu ders, belge ağacının
içinde ayrı bir ağaç açan ve iki yönlü yalıtımı kuran mekanizmayı kurar.

#### Gölge Kökü, Konak ve Işık Ağacı

Bir öge, kendisine bağlı ikinci bir ağaç edinebilir. Bu ağacın köküne **gölge kökü (shadow
root)**, ağacın kendisine **gölge ağacı (shadow tree)**, ağacı taşıyan ögeye **konak (host)**
denir.

Gölge ağacı belge ağacının bir parçası değildir; ayrı bir ağaçtır ve kendi kökü vardır.
Konak, iki ağacın birleştiği tek noktadır: belge ağacında bir öge, gölge ağacında bir kök
sahibi.

Konağın belgede yazılı kendi çocukları kaybolmaz; oldukları yerde dururlar. Bunlara
**ışık ağacı (light DOM)** denir. Gölge ağacı takılı bir ögede çizilen şey ışık ağacı değil
gölge ağacıdır; ışık ağacının içeriğinin nasıl geri kazanıldığı sonraki dersin konusudur.

Bir ögeye gölge ağacı takmak her etiket için mümkün değildir. Kendi iç yapısı olan ögeler
— form denetimleri, görsel, giriş alanı — buna kapalıdır; bunların zaten tarayıcı
tarafından yönetilen bir iç yapısı vardır.

#### Seçicinin Durduğu Sınır

Yalıtımın ilk yüzü seçicidir. Belge ağacında yapılan bir sorgu gölge ağacına inmez; gölge
ağacında yapılan bir sorgu dışarı çıkmaz.

```js
// golge-sinir.mjs — secici sinirda durur, kalitim sinirdan gecer
const oge = (ad, ozn = {}, ...cocuklar) => {
  const d = { ad, ozn, cocuklar, ebeveyn: null, golge: null };
  for (const c of cocuklar) c.ebeveyn = d;
  return d;
};

// Golge kokunu konaga takar.
function golgeTak(konak, ...cocuklar) {
  const kok = { ad: "#golge-kok", ozn: {}, cocuklar, ebeveyn: null, konak, golge: null };
  for (const c of cocuklar) c.ebeveyn = kok;
  konak.golge = kok;
  return kok;
}

// Bir agacta dolasir; karsilastigi golge agaclarinin icine GIRMEZ.
function* agacta(kok) {
  yield kok;
  for (const c of kok.cocuklar) yield* agacta(c);
}

const sinifVar = (d, s) => (d.ozn.class ?? "").split(/\s+/).includes(s);
const yaz = (d) => `${d.ad}#${d.ozn.id ?? "-"}`;
const sorgu = (kok, sinif) => [...agacta(kok)].filter((d) => sinifVar(d, sinif)).map(yaz);

// Istasyon sayfasi: iki rozet; ilkinin golge agaci var, ikincisi duz isaretleme.
const rozetA = oge("olcum-rozeti", { class: "rozet", id: "sicaklik" });
golgeTak(rozetA,
  oge("div", { class: "kutu", id: "g-kutu" },
    oge("span", { class: "deger", id: "g-deger" }),
    oge("span", { class: "birim", id: "g-birim" })));

const rozetB = oge("p", { class: "rozet", id: "nem" },
  oge("span", { class: "deger", id: "l-deger" }));

const belge = oge("#belge", { id: "belge" }, oge("section", { id: "pano" }, rozetA, rozetB));

console.log("belge agacindan .rozet :", sorgu(belge, "rozet"));
console.log("belge agacindan .deger :", sorgu(belge, "deger"));
console.log("golge agacindan .deger :", sorgu(rozetA.golge, "deger"));
console.log("golge agacindan .rozet :", sorgu(rozetA.golge, "rozet"));

// Kalitilan ozellikler sinirdan gecer; secici tabanli kurallar gecmez.
const KALITILAN = new Set(["color", "font-size", "line-height"]);
const belgeKurali = { sinif: "deger", bildirim: { "font-weight": "700" } };
const konaktanGelen = { color: "#b23", "font-weight": "400" };

function hesapla(dugum) {
  const deger = Object.fromEntries(
    Object.entries(konaktanGelen).filter(([ad]) => KALITILAN.has(ad)));
  const belgeAgacinda = [...agacta(belge)].includes(dugum);
  if (belgeAgacinda && sinifVar(dugum, belgeKurali.sinif)) Object.assign(deger, belgeKurali.bildirim);
  return deger;
}

const golgeDeger = [...agacta(rozetA.golge)].find((d) => d.ozn.id === "g-deger");
const belgeDeger = [...agacta(belge)].find((d) => d.ozn.id === "l-deger");

console.log("hesaplanan span#l-deger:", hesapla(belgeDeger));
console.log("hesaplanan span#g-deger:", hesapla(golgeDeger));
```

```
belge agacindan .rozet : [ 'olcum-rozeti#sicaklik', 'p#nem' ]
belge agacindan .deger : [ 'span#l-deger' ]
golge agacindan .deger : [ 'span#g-deger' ]
golge agacindan .rozet : []
hesaplanan span#l-deger: { color: '#b23', 'font-weight': '700' }
hesaplanan span#g-deger: { color: '#b23' }
```

İlk dört satır sınırın simetrik olduğunu gösterir. Belgede `.deger` arayan sorgu iki
eşleşmeden yalnızca birini bulur; gölge ağacındaki `span#g-deger` görünmez. Ters yönde de
aynısı geçerlidir: gölge ağacında `.rozet` arayan sorgu, konağın kendisi o sınıfa sahip
olduğu hâlde boş döner — konak gölge ağacının dışındadır.

Bunun pratik iki sonucu vardır. Birincisi, gölge ağacındaki kimlikler belge kimlikleriyle
çakışmaz; her gölge ağacı kendi kimlik uzayına sahiptir. Aynı bileşeni sayfada on kez
kullanmak on tane aynı kimliği üretse bile çakışma doğmaz. İkincisi, bileşenin içine
dokunmak isteyen dış kod bunu ancak konaktan gölge köküne geçerek yapabilir — yani
kasıtlı ve görünür bir adımla.

#### Biçemin İki Yönlü Yalıtımı

Yalıtımın ikinci yüzü biçemdir ve seçiciyle aynı kuraldan doğar. Basamaklama ve Özgüllük
dersinde tanımlanan eşleme, bir kuralın kendi ağacındaki ögelere uygulanmasıdır. Belgeye
yazılmış bir kuralın seçicisi gölge ağacındaki ögelere eşleşmez; gölge ağacına yazılmış bir
kural belge ağacına eşleşmez.

Çıktının son iki satırı bu ayrımı gösterir. Belge ağacındaki `span#l-deger` ögesine belge
kuralındaki `font-weight: 700` uygulanmış, gölge ağacındaki `span#g-deger` ögesine
uygulanmamıştır.

Sınırın biçem tarafında **iki tanımlı geçiş** vardır ve her ikisi de kasıtlıdır.

Birincisi **kalıtımdır**. Kalıtılan özellikler ağaç sınırını tanımaz: konağa uygulanan
yazı rengi, satır yüksekliği, yazı boyutu gölge ağacına devreder. Çıktıda her iki ögenin
de `color` değerini konaktan almış olması budur. Bu yüzden gölge ağacı biçemi tam bir
sıfırlama değildir; bileşen dışarıdan gelen tipografiye uyum sağlar ve bu genellikle
istenen davranıştır.

İkincisi **özel özelliklerdir**. Kalıtılan bir değer olarak tanımlandıkları için sınırdan
geçerler ve bileşene dışarıdan ayarlanabilir bir yüzey kazandırırlar. Bileşen, kendi
kurallarında bir özel özelliği yedekli değeriyle okur; sayfa o özelliği konakta
tanımlayarak bileşenin görünümünü, iç yapısına hiç dokunmadan değiştirir. Özel Özellikler
dersinde kurulan bu mekanizma, gölge ağacı ile birlikte bir bileşenin **biçem sözleşmesi**
hâline gelir.

Gölge ağacının içinden yazılan kurallar için iki seçici tanımlıdır. Biri konağın kendisini
hedefler ve bileşenin dış kutusunu biçemler; koşullu biçimi, konağın kendi özniteliğine göre
farklı biçem vermeyi sağlar; konağın belgedeki bağlamına bakmak ayrı bir seçicinin işidir. Diğeri,
bileşenin dışarıya açtığı iç parçaları hedefler: bileşen bir iç ögeyi ad vererek işaretler,
dışarıdaki biçem o adı kullanarak yalnızca işaretlenmiş parçalara erişir. Bu, kapsüllemeyi delmek
değil, kapsüllemenin içinde tanımlı bir pencere açmaktır — bileşen hangi parçalarının biçemlenmeye
açık olduğuna kendisi karar verir.

#### Açık ve Kapalı Kip

Gölge ağacı takılırken iki kipten biri seçilir. **Açık kipte** konağın gölge kökü program
tarafından okunabilir; dışarıdaki kod bileşenin içine erişebilir. **Kapalı kipte** konak
üzerinden gölge köküne erişilemez; başvuru yalnızca bileşenin kendi kodunda tutulur.

Kapalı kip bir güvenlik sınırı değildir. Bileşenin kodu sayfayla aynı ortamda çalışır;
kararlı bir kod gölge köküne başka yollardan ulaşabilir. Kapalı kip bir **niyet
bildirimidir**: bileşenin iç yapısı arayüzünün parçası değildir, ona dayanan kod
kırılacaktır.

Bedeli de vardır. Kapalı bir ağaç, sayfanın erişilebilirlik denetimlerini, test
araçlarını ve hata ayıklamayı zorlaştırır. Yaygın seçim açık kiptir; kapsülleme sözleşmeyle
korunur, erişim engeliyle değil.

#### Olayların Sınırı Aşması

Olay Modeli dersinde yayılım yolu kökten hedefe uzanan ata zinciri olarak tanımlanmıştı.
Gölge ağacı bu zincire bir ek getirir: yol, gölge kökünde durmaz, konağa atlar ve belgeye
kadar sürer. Buna **birleşik yol (composed path)** denir.

Yol dışarı taştığında bir sorun doğar. Belgeye bağlanmış bir dinleyici, olayın hedefi
olarak gölge ağacının içindeki bir düğümü görürse kapsülleme delinmiş olur. Bu yüzden
tarayıcı hedefi **çevirir (retargeting)**: her dinleyici, olayın hedefi olarak kendi
ağacındaki en yakın karşılığını görür.

```js
// hedef-cevirme.mjs — olay golge sinirini asarken hedef nasil degisir
const oge = (ad, ...cocuklar) => {
  const d = { ad, cocuklar, ebeveyn: null, konak: null, golge: null };
  for (const c of cocuklar) c.ebeveyn = d;
  return d;
};

function golgeTak(konak, ...cocuklar) {
  const kok = { ad: "#golge-kok", cocuklar, ebeveyn: null, konak, golge: null };
  for (const c of cocuklar) c.ebeveyn = kok;
  konak.golge = kok;
  return kok;
}

// Bir dugumun ait oldugu agacin koku: ya belge ya bir golge kok.
const agacKoku = (d) => { let a = d; while (a.ebeveyn) a = a.ebeveyn; return a; };

// Birlesik yol: hedeften baslar, agac koklerinde konaga atlayarak belgeye kadar cikar.
function birlesikYol(hedef) {
  const yol = [];
  for (let d = hedef; d; d = d.ebeveyn ?? d.konak) yol.push(d);
  return yol;
}

// Bir dinleyici icin hedef: birlesik yolda dinleyiciyle AYNI agacta olan ilk dugum.
function cevrilmisHedef(hedef, dinleyici) {
  const dinleyiciAgaci = agacKoku(dinleyici);
  return birlesikYol(hedef).find((d) => agacKoku(d) === dinleyiciAgaci);
}

const dugme = oge("button.kaydet");
const kutu = oge("div.kutu", dugme);
const rozet = oge("olcum-rozeti");
const golgeKok = golgeTak(rozet, kutu);
const pano = oge("section#pano", rozet);
const belge = oge("#belge", pano);

console.log("birlesik yol:", birlesikYol(dugme).map((d) => d.ad).join(" → "));
console.log();
console.log("dinleyicinin dugumu   hedef olarak gordugu");
for (const d of [dugme, kutu, golgeKok, rozet, pano, belge])
  console.log(d.ad.padEnd(22), cevrilmisHedef(dugme, d).ad);

// composed: false olan olay golge kokunden yukari cikmaz.
const yayilanlar = (hedef, birlesik) => {
  const yol = birlesikYol(hedef);
  if (birlesik) return yol;
  const sinir = yol.findIndex((d) => d.ad === "#golge-kok");
  return sinir === -1 ? yol : yol.slice(0, sinir + 1);
};

console.log();
console.log("composed: true  →", yayilanlar(dugme, true).map((d) => d.ad).join(" → "));
console.log("composed: false →", yayilanlar(dugme, false).map((d) => d.ad).join(" → "));
```

```
birlesik yol: button.kaydet → div.kutu → #golge-kok → olcum-rozeti → section#pano → #belge

dinleyicinin dugumu   hedef olarak gordugu
button.kaydet          button.kaydet
div.kutu               button.kaydet
#golge-kok             button.kaydet
olcum-rozeti           olcum-rozeti
section#pano           olcum-rozeti
#belge                 olcum-rozeti

composed: true  → button.kaydet → div.kutu → #golge-kok → olcum-rozeti → section#pano → #belge
composed: false → button.kaydet → div.kutu → #golge-kok
```

Gölge ağacının içindeki üç dinleyici gerçek hedefi, dışındaki üç dinleyici konağı görür.
Belgeye bağlanmış bir olay devri kodu, bileşenin içinde hangi düğmeye tıklandığını
öğrenemez; yalnızca "ölçüm rozetine tıklandı" bilgisini alır. Bileşenin dışarıya ne
bildireceğine kendisi karar verir ve bunu genellikle kendi tanımladığı bir olay türüyle
yapar.

Son iki satır ikinci ayrımı gösterir. Bir olayın gölge sınırını aşıp aşmayacağı olay
nesnesinin bir alanıyla belirlenir. Sınırı aşmayan bir olay gölge kökünde durur ve dışarıya
hiç ulaşmaz. Kullanıcı girdisinden doğan yerleşik olaylar sınırı aşar; bileşenin kendi
tanımladığı olaylar ise varsayılan olarak aşmaz ve dışarı çıkması isteniyorsa bu açıkça
bildirilir.

#### Özet

- Gölge ağacı, konak ögesine bağlı ayrı bir belge ağacıdır; konağın belgedeki kendi
  çocukları ışık ağacı olarak yerinde kalır.
- Seçici sınırı iki yönde de aşmaz: belge sorgusu gölge ağacını, gölge sorgusu belge
  ağacını görmez. Kimlik uzayları da ayrıdır.
- Biçem eşlemesi seçiciyle aynı sınıra uyar; kalıtılan özellikler ve özel özellikler ise
  sınırdan geçer ve bileşenin biçem sözleşmesini oluşturur.
- Bileşen, dışarıdan biçemlenebilecek iç parçalarını ad vererek açar; kapsülleme bu
  pencerelerin dışında korunur.
- Kapalı kip erişimi zorlaştırır ama bir güvenlik sınırı değildir; taşıdığı bilgi, iç
  yapının arayüzün parçası olmadığıdır.
- Olay yolu gölge kökünden konağa atlayarak sürer; her dinleyici hedefi kendi ağacındaki
  karşılığıyla görür ve sınırı aşmayan olaylar gölge kökünde durur.

#### Sonraki Adım

Bu ders gölge ağacının nasıl yalıtıldığını gösterdi, ama içeriğinin nereden geldiğini
göstermedi. İki soru açık kaldı. Birincisi, bileşenin iç yapısı her örnek için düğüm düğüm
kurulursa on beş rozet on beş kez aynı kurulum kodunu çalıştırır; işaretlemenin çizilmeden
duran ve ucuz çoğaltılabilen bir biçimi gerekir. İkincisi, konağın belgede yazılı ışık
ağacı şu an hiçbir yerde görünmüyor; bileşenin dışarıdan aldığı içeriği gölge ağacının
belirlediği bir yere yerleştirmesi gerekir. Sonraki ders bu iki mekanizmayı — çizilmeyen
işaretleme kabını ve içeriğin yerleştirildiği noktaları — birlikte kurar.

### Şablonlar ve Yuvalar

Source: https://academia.sh/tr/kurslar/tarayici-platformu/sablonlar-ve-yuvalar

Çizilmeden duran işaretleme kabı ve kopyalanmasının kuralları; yuva atama algoritması, yedek içerik, düzleştirilmiş ağaç ve yerleştirilen içeriğin hangi ağaca ait kaldığı.

Önceki ders gölge ağacının nasıl yalıtıldığını gösterdi, içeriğinin nereden geldiğini
göstermedi. İki açık soru kaldı. Birincisi, bileşenin iç yapısı her örnek için düğüm düğüm
kurulursa on beş rozet on beş kez aynı kurulum kodunu çalıştırır. İkincisi, konağın belgede
yazılı ışık ağacı hiçbir yerde görünmüyor: rozetin içine yazılan başlık ve birim kayboluyor.

Bu ders iki mekanizmayı birlikte kurar: çizilmeden duran bir işaretleme kabı ve ışık
ağacındaki içeriğin gölge ağacında yerleştirileceği noktalar.

#### Çizilmeyen İşaretleme Kabı

HTML'de bir öge vardır ki içeriği ayrıştırılır ama belgeye girmez: **şablon (template)**.
Ayrıştırıcı şablonun içindeki işaretlemeyi düğümlere çevirir, ancak bu düğümleri belge
ağacına değil şablona ait ayrı bir belge parçasına koyar.

Ayrımın sonucu içeriğin **edilgen** olmasıdır. Şablonun içindeki görsel indirilmez,
içindeki betik çalışmaz, içindeki biçem kuralları uygulanmaz, içindeki ögeler yerleşim
hesabına girmez. İşaretleme sayfada yazılıdır ama sayfanın hiçbir davranışına katılmaz.

Bu, bileşenin iç yapısını taşımak için doğru kaptır. İşaretleme belgede okunabilir biçimde
durur; bileşen gerektiğinde kabın içeriğini kopyalar ve kopyayı gölge ağacına koyar.
İçerik dizgiden her seferinde yeniden ayrıştırılmaz — ayrıştırma bir kez olmuştur, sonrası
ağaç kopyalamadır.

Şablonun içeriğine erişmek için ögenin çocuklarına bakmak sonuç vermez; içerik ayrı bir
parçadadır ve o parça üzerinden okunur. Bu, ilk karşılaşmada şaşırtıcı bulunan davranıştır:
şablon ögesinin çocuk düğüm sayısı sıfırdır.

#### Klonlama ve Klonun Taşımadıkları

Kopyalama derin olmalıdır: yalnızca kökü değil bütün alt ağacı kopyalayan bir işlem. Sığ
kopya şablonun kökünü verir, içeriğini vermez.

Kopyanın taşımadıkları, taşıdıklarından daha öğreticidir.

**Dinleyiciler kopyalanmaz.** Şablondaki bir düğüme dinleyici bağlanmış olsa bile kopya
onu almaz; dinleyici, kopyalanan ağaç yerine yerleştirildikten sonra bağlanır. Bu bir
eksiklik değil zorunluluktur: dinleyici bir işlev başvurusudur ve işaretlemenin parçası
değildir.

**Kimlikler kopyalanır.** Şablonda bir kimlik yazılıysa her kopyada aynı kimlik oluşur.
Belge ağacında bu, kimlik benzersizliğini bozar. Gölge ağacında ise sorun doğmaz: önceki
derste görüldüğü gibi her gölge ağacının kendi kimlik uzayı vardır. Şablonlarda kimlik
kullanımının gölge ağacına bağlı olması bundandır.

**Öznitelikler kopyalanır, kullanıcı durumu kopyalanmaz.** DOM API'si dersindeki ayrım
burada da geçerlidir: kopya, özniteliklerin bildirdiği başlangıç durumunu alır; kaynak
ögede kullanıcının değiştirdiği değer kopyaya geçmez.

Kopyalama, düğümleri belgeye bağlı olmayan bir parçada üretir. DOM API'si dersinde
kurulan kural burada kendiliğinden işler: kopya hazırlanırken hiçbir yerleşim hesabı
tetiklenmez, hesap yalnızca parça gölge ağacına eklendiğinde bir kez yapılır.

#### Yuvalar ve Atama Kuralı

Şablon bileşenin **kendi** yapısını verir. Bileşenin dışarıdan aldığı içerik başka bir
yerden gelir: konağın ışık ağacından. Gölge ağacı, bu içeriğin yerleştirileceği noktaları
**yuva (slot)** ögeleriyle işaretler.

Kural üç maddeye iner. Yalnızca konağın **doğrudan çocukları** atanabilir; torunlar
atanmaz. `slot` özniteliği taşıyan çocuk aynı adlı yuvaya, taşımayan çocuk adsız
varsayılan yuvaya gider. Hiçbir yuvanın adıyla eşleşmeyen çocuk hiçbir yere atanmaz ve
çizilmez.

```js
// yuva-atama.mjs — yuva atama algoritmasi ve duzlestirilmis agac
const oge = (ad, ozn = {}, ...cocuklar) => ({ ad, ozn, cocuklar, metin: null });
const metin = (deger) => ({ ad: "#metin", ozn: {}, cocuklar: [], metin: deger });
const yuva = (ad, ...yedek) => oge("slot", ad ? { name: ad } : {}, ...yedek);

function* agacta(kok) { yield kok; for (const c of kok.cocuklar) yield* agacta(c); }

const etiket = (d) =>
  d.metin !== null ? `"${d.metin}"`
  : d.ad === "slot" ? `slot[${d.ozn.name ?? "varsayilan"}]`
  : d.ad + (d.ozn.slot ? `[slot=${d.ozn.slot}]` : "");

// Atama: yalnizca konagin DOGRUDAN cocuklari atanir; slot ozniteligi olmayanlar
// varsayilan yuvaya, olanlar ayni adli yuvaya gider. Sira isik agacinin sirasidir.
function atamalar(konak, golgeKok) {
  const harita = new Map();
  for (const d of agacta(golgeKok)) if (d.ad === "slot") harita.set(d.ozn.name ?? "", []);
  for (const cocuk of konak.cocuklar) {
    const istenen = cocuk.ozn.slot ?? "";
    if (harita.has(istenen)) harita.get(istenen).push(cocuk);
  }
  return harita;
}

// Duzlestirilmis agac: her yuva, atanan dugumlerle; atama yoksa yedek icerigiyle degisir.
function duzlestir(dugum, harita, derinlik = 0, satirlar = []) {
  if (dugum.ad === "slot") {
    const atanan = harita.get(dugum.ozn.name ?? "") ?? [];
    const gorunen = atanan.length ? atanan : dugum.cocuklar;
    const kaynak = atanan.length ? "isik" : "yedek";
    for (const d of gorunen) {
      satirlar.push(`${"  ".repeat(derinlik)}${etiket(d)}   (${kaynak})`);
      for (const c of d.cocuklar) duzlestir(c, harita, derinlik + 1, satirlar);
    }
    return satirlar;
  }
  satirlar.push(`${"  ".repeat(derinlik)}${etiket(dugum)}`);
  for (const c of dugum.cocuklar) duzlestir(c, harita, derinlik + 1, satirlar);
  return satirlar;
}

// Konagin belgede yazili cocuklari (isik agaci)
const konak = oge("olcum-rozeti", { id: "sicaklik" },
  oge("span", { slot: "ad" }, metin("Sıcaklık")),
  oge("strong", {}, metin("-4.2")),
  oge("span", { slot: "birim" }, metin("°C")),
  oge("span", { slot: "kaynak" }, metin("istasyon-3")));

// Golge agaci
const golgeKok = oge("#golge-kok", {},
  oge("div", { class: "kutu" },
    yuva("ad", metin("Ölçüm")),
    oge("output", {}, yuva(null, metin("—"))),
    yuva("birim", metin("birimsiz"))));

const harita = atamalar(konak, golgeKok);
console.log("atamalar:");
for (const [ad, dugumler] of harita)
  console.log(`  slot[${ad || "varsayilan"}] ←`, dugumler.map(etiket).join(", ") || "(bos → yedek icerik)");

const atanmamis = konak.cocuklar.filter((c) => !harita.has(c.ozn.slot ?? ""));
console.log("atanmayan isik dugumleri:", atanmamis.map(etiket).join(", ") || "(yok)");

console.log("\nduzlestirilmis agac:");
console.log(duzlestir(golgeKok, harita).join("\n"));

// Isik agaci bos bir konak ayni golge agaciyla: her yuva yedek icerigini gosterir.
const bosKonak = oge("olcum-rozeti", { id: "nem" });
console.log("\nisik agaci bos konak:");
console.log(duzlestir(golgeKok, atamalar(bosKonak, golgeKok)).join("\n"));

// Isik agaci degisirse atama yeniden hesaplanir.
konak.cocuklar.push(oge("em", { slot: "birim" }, metin("°F")));
const yeni = atamalar(konak, golgeKok);
console.log("\nisik agacina em[slot=birim] eklendikten sonra:");
for (const [ad, dugumler] of yeni) {
  const oncekiler = harita.get(ad).map(etiket).join(",");
  const simdikiler = dugumler.map(etiket).join(",");
  console.log(`  slot[${ad || "varsayilan"}] ${oncekiler === simdikiler ? "degismedi" : "degisti → " + (simdikiler || "(bos)")}`);
}
```

```
atamalar:
  slot[ad] ← span[slot=ad]
  slot[varsayilan] ← strong
  slot[birim] ← span[slot=birim]
atanmayan isik dugumleri: span[slot=kaynak]

duzlestirilmis agac:
#golge-kok
  div
    span[slot=ad]   (isik)
      "Sıcaklık"
    output
      strong   (isik)
        "-4.2"
    span[slot=birim]   (isik)
      "°C"

isik agaci bos konak:
#golge-kok
  div
    "Ölçüm"   (yedek)
    output
      "—"   (yedek)
    "birimsiz"   (yedek)

isik agacina em[slot=birim] eklendikten sonra:
  slot[ad] degismedi
  slot[varsayilan] degismedi
  slot[birim] degisti → span[slot=birim],em[slot=birim]
```

Çıktının ilk bölümünde dikkat çeken satır sonuncusudur: `slot="kaynak"` yazan çocuk hiçbir
yuvayla eşleşmediği için atanmamış, dolayısıyla çizilmemiştir. Ağaçtan silinmemiştir,
belgede durmaktadır; yalnızca düzleştirilmiş ağaca girmemiştir. Bu, bileşen kullanıcısının
yaptığı yazım hatalarının sessiz kaybolmasıyla sonuçlanır ve bileşenin belgelenmiş yuva
adlarına bağımlılığını gösterir.

#### Düzleştirilmiş Ağaç

Tarayıcının gerçekten çizdiği şey ne belge ağacı ne gölge ağacıdır; her yuvanın kendisine
atanan düğümlerle değiştirilmiş hâlidir. Buna **düzleştirilmiş ağaç (flattened tree)**
denir ve yerleşim ile boyama bu ağaç üzerinden yapılır.

Çıktının ikinci bölümü bunu gösterir. `slot` ögeleri düzleştirilmiş ağaçta görünmez;
yerlerinde ışık ağacından gelen düğümler durur. `strong` ögesi belge ağacında konağın
doğrudan çocuğudur, ama çizim sırasında `output` ögesinin içindedir. Düğümün ağaçtaki yeri
ile çizimdeki yeri ayrışmıştır.

Üçüncü bölüm **yedek içeriğin (fallback content)** rolünü gösterir. Bir yuvanın kendi
çocukları, o yuvaya hiçbir şey atanmadığında çizilir. Bu, bileşene varsayılan bir görünüm
kazandırır: ışık ağacı boş bir rozet birimsiz ve değersiz değil, yer tutucularıyla
görünür. Yedek içerik atama olduğu anda tümüyle devre dışı kalır; atananla karışmaz.

Son bölüm ışık ağacındaki değişikliğin atamayı yeniden hesaplattığını gösterir. Yuva
ögeleri, atamaları değiştiğinde bir olay yayar; içeriğine bağlı bir hesap yapması gereken
bileşen bu olayı dinler. Olayın çizim döngüsü içindeki yerini Zamanlama ve Çizim Döngüsü
dersindeki model verir: atama değişimi, değişikliği yapan koddan sonra bildirilir.

#### Yerleştirilen İçeriğin Sahibi

Yerleştirme, düğümün ağacını değiştirmez. Yuvaya atanan `strong` ögesi belge ağacında
kalır; yalnızca çizilirken gölge ağacındaki bir noktaya taşınır. Sahiplik değişmediği için
üç davranış buradan türer.

**Biçem belgeden gelir.** Yerleştirilen düğüm belge ağacının kuralı ile biçemlenir;
gölge ağacındaki kurallar ona genel olarak eşleşmez. Bileşen, yerleştirilen düğümlere
sınırlı bir biçem verebilir: yuvaya atanmış düğümleri hedefleyen özel bir seçici vardır ve
yalnızca atanmış düğümün kendisine — çocuklarına değil — eşleşir. Bu kısıt bilinçlidir:
bileşen, kullanıcısının verdiği içeriğin iç yapısını biçemleyemez, yalnızca dış kutusunu
hizalayabilir. Çakışmada belgenin kuralı üstün gelir; bileşenin yerleştirilen içeriğe
verdiği biçem bir öneridir.

**Olaylar sınır aşımı yaşamaz.** Yerleştirilen düğümün ataları belge ağacındadır; oradaki
bir olayın hedefi çevrilmez, çünkü olay hiçbir gölge sınırı geçmemiştir. Bileşen, ışık
ağacındaki içeriğe bağlanmış dinleyicileri göremez ve görmemelidir.

**Erişilebilirlik ağacı düzleştirilmiş ağaçtan türer.** Yardımcı teknolojiye sunulan yapı,
kullanıcının gördüğü sıradır. Yuvaların gölge ağacındaki sırası okuma sırasını belirler;
ışık ağacındaki yazım sırası değil. Bileşen tasarlarken yuva sırası bir görsel tercih
değil, anlam taşıyan bir karardır.

#### Özet

- Şablon ögesinin içeriği ayrıştırılır ama belgeye girmez; edilgendir, hiçbir kaynak
  yüklemez ve çizime katılmaz.
- Şablon içeriği derin kopyayla çoğaltılır; kopya öznitelikleri ve kimlikleri taşır,
  dinleyicileri ve kullanıcı durumunu taşımaz.
- Yuva atama kuralı yalnızca konağın doğrudan çocuklarına uygulanır; eşleşmeyen ad hiçbir
  yere atanmaz ve çizilmez.
- Çizilen yapı düzleştirilmiş ağaçtır: gölge ağacındaki her yuva, atanan düğümlerle ya da
  atama yoksa yedek içeriğiyle değiştirilmiş hâlidir.
- Yerleştirilen düğüm ışık ağacına ait kalır: belgenin biçemiyle biçemlenir, olayları
  gölge sınırı geçmez; bileşenin ona verebileceği biçem sınırlıdır.

#### Sonraki Adım

Buraya kadar kurulan her şey sayfa yüklendikten sonra geçerlidir: öge tanımlanır, gölge
ağacı takılır, yuvalar dolar. Hepsinin öncesinde tek bir varsayım durur — bileşenin kodu,
şablonu ve verisi ağdan gelmiştir. Ağ bağlantısı zayıfladığında ya da tümüyle kesildiğinde
bu varsayım düşer ve sayfa, ne kadar iyi kapsüllenmiş olursa olsun, boş bir hata ekranına
iner. İstasyon sayfasının kullanıldığı yer buna elverişli değildir: ölçüm alan kişi
çoğunlukla kapsama alanının kıyısındadır. Sonraki ders, sayfanın isteklerini ağa
ulaşmadan önce karşılayabilen ve sayfadan bağımsız yaşayan bir katmanı kurar.

### Servis Çalışanları

Source: https://academia.sh/tr/kurslar/tarayici-platformu/servis-calisanlari

Sayfadan bağımsız yaşayan ve isteklere araya girerek yanıt verebilen katman; kapsam kuralı, kurulum–bekleme–etkinleşme döngüsü, istek karşılama sözleşmesi ve yanlış dağıtımın geri alınma yolu.

Buraya kadar kurulan her şey sayfa yüklendikten sonra geçerlidir. Hepsinin öncesinde tek
bir varsayım durur: bileşenin kodu, şablonu ve verisi ağdan gelmiştir. Bağlantı
zayıfladığında bu varsayım düşer ve sayfa, ne kadar iyi kapsüllenmiş olursa olsun, bir hata
ekranına iner. Kuzey Yamaç Ölçüm İstasyonu sayfasının kullanıldığı yer buna elverişli
değildir: ölçüm alan kişi çoğunlukla kapsama alanının kıyısındadır.

Bu ders, sayfanın isteklerini ağa ulaşmadan önce görebilen ve sayfadan bağımsız yaşayan
bir katmanı kurar: **servis çalışanı (service worker)**.

#### Sayfadan Bağımsız Bir Bağlam

Servis çalışanı, Web İşçileri dersinde tanıtılan çalışan iş parçacığının bir türüdür ve
onun bütün kısıtlarını taşır: belge ağacına erişimi yoktur, sayfanın değişkenlerini
göremez, sayfayla yalnızca ileti geçirerek konuşur. Üzerine kendi kısıtlarını ekler.

**Bir sayfaya bağlı değildir.** Kaydeden sayfa kapansa da kayıt yerinde kalır; aynı
kökenin başka bir sayfası açıldığında aynı çalışan devreye girer. Bu, onu bir sayfa
bileşeni değil, kökene ait bir hizmet yapar.

**Sürekli çalışmaz.** Tarayıcı çalışanı bir olay geldiğinde uyandırır, iş bitince
sonlandırır. Aradaki süre garanti edilmez. Buradan bağlayıcı bir kural çıkar: **çalışanın
genel kapsamındaki değişkenler kalıcı durum tutamaz.** İki olay arasında kaybolabilirler.
Kalıcı olması gereken her şey depolama katmanına yazılır.

**Yalnızca güvenli bağlamda çalışır.** Kayıt, HTTPS üzerinden sunulan bir **köken (origin)**
gerektirir; geliştirme kolaylığı için geri döngü adresi ayrık tutulmuştur. Gerekçe
doğrudandır: araya giren bir katman, ağı dinleyen birinin eline geçtiğinde sayfanın
tamamını kalıcı olarak değiştirebilir. HTTPS'in Rolü dersindeki bütünlük güvencesi bu
yetkinin önkoşuludur.

#### Kapsam ve Denetim

Bir kayıt, kökenin tamamını değil bir yol önekini yönetir. Bu öneke **kapsam (scope)** denir
ve iki ayrı kural onu belirler.

Birincisi bildirimin geçerliliğidir: bir çalışan, betiğinin bulunduğu dizinden daha geniş
bir kapsam bildiremez. `/varlik/js/` altındaki bir betik kökün tamamını yönetemez. Kısıt
aşılabilir, ama yalnızca sunucunun bu izni bir başlıkla vermesiyle — yani dosyayı yükleyen
kişinin değil, sunucuyu yapılandıran kişinin kararıyla.

İkincisi eşlemedir: bir sayfayı, kapsamı o sayfanın yolunun öneki olan kayıtlardan **en
uzunu** denetler. IP Adresi Kavramı dersindeki en uzun önek eşlemesiyle aynı mantıktır.

```js
// kapsam.mjs — kayit kapsaminin en uzun onek kuraliyla eslenmesi
// Kapsam, betigin bulundugu dizinden genis olamaz; genisletmek icin sunucunun
// izin basligi gerekir. Asagidaki iki fonksiyon bu kurallari ayri ayri sinar.

const dizin = (yol) => yol.slice(0, yol.lastIndexOf("/") + 1);

function kapsamGecerliMi(betikYolu, istenenKapsam, izinVerilenUst = null) {
  const enGenis = izinVerilenUst ?? dizin(betikYolu);
  return istenenKapsam.startsWith(enGenis);
}

// Bir sayfayi hangi kayit denetler: kapsami sayfanin yolunun oneki olan
// kayitlardan EN UZUN olani.
function denetleyen(kayitlar, sayfaYolu) {
  const uyanlar = kayitlar.filter((k) => sayfaYolu.startsWith(k.kapsam));
  if (uyanlar.length === 0) return null;
  return uyanlar.reduce((a, b) => (b.kapsam.length > a.kapsam.length ? b : a));
}

const denemeler = [
  { betik: "/sw.js", kapsam: "/" },
  { betik: "/istasyon/sw.js", kapsam: "/istasyon/" },
  { betik: "/istasyon/sw.js", kapsam: "/" },
  { betik: "/istasyon/sw.js", kapsam: "/", izin: "/" },
  { betik: "/varlik/js/sw.js", kapsam: "/istasyon/" },
];

console.log("kapsam bildirimi gecerli mi?");
for (const d of denemeler) {
  const sonuc = kapsamGecerliMi(d.betik, d.kapsam, d.izin);
  console.log(
    `  betik ${d.betik.padEnd(18)} kapsam ${d.kapsam.padEnd(12)}` +
    `${d.izin ? "izin " + d.izin : "izin yok"} → ${sonuc ? "gecerli" : "reddedilir"}`);
}

const kayitlar = [
  { ad: "kok", kapsam: "/" },
  { ad: "istasyon", kapsam: "/istasyon/" },
  { ad: "kuzey-yamac", kapsam: "/istasyon/kuzey-yamac/" },
];

console.log("\nsayfayi hangi kayit denetler?");
for (const sayfa of [
  "/",
  "/hakkinda",
  "/istasyon/",
  "/istasyon/gunluk",
  "/istasyon/kuzey-yamac/olcumler",
]) console.log(`  ${sayfa.padEnd(32)} → ${denetleyen(kayitlar, sayfa)?.ad ?? "(denetimsiz)"}`);
```

```
kapsam bildirimi gecerli mi?
  betik /sw.js             kapsam /           izin yok → gecerli
  betik /istasyon/sw.js    kapsam /istasyon/  izin yok → gecerli
  betik /istasyon/sw.js    kapsam /           izin yok → reddedilir
  betik /istasyon/sw.js    kapsam /           izin / → gecerli
  betik /varlik/js/sw.js   kapsam /istasyon/  izin yok → reddedilir

sayfayi hangi kayit denetler?
  /                                → kok
  /hakkinda                        → kok
  /istasyon/                       → istasyon
  /istasyon/gunluk                 → istasyon
  /istasyon/kuzey-yamac/olcumler   → kuzey-yamac
```

Üçüncü ve dördüncü satırdaki fark, betik yerleşiminin bir dağıtım kararı olduğunu gösterir.
Bir servis çalışanı betiğini varlık dizinine koymak, paketleyicinin çıktısını tek yere
toplamak için doğal görünür ve kapsamı o dizine hapseder. Betiğin kök dizinde durması
teknik bir zorunluluk değil, kapsam bildiriminin bedava geldiği yerdir.

Kapsam eşlemesinin bir sonucu da denetimsiz kalabilmektir: kapsam dışındaki bir sayfa
hiçbir çalışan tarafından görülmez ve ağa doğrudan gider. Kapsam dışı sayfaların
çevrimdışı davranışı yoktur.

#### Yaşam Döngüsü ve Bekleme Durumu

Bir servis çalışanı kaydedildiğinde doğrudan işe başlamaz. Aradaki durumlar, aynı sayfada
iki farklı sürümün karışmasını önlemek için vardır.

**Kurulum** yeni sürümün ilk fırsatıdır: ihtiyaç duyacağı kaynakları önceden toplar.
Kurulum başarısız olursa sürüm hiç etkinleşmez ve eski sürüm yerinde kalır — bu, yarım
kalmış bir dağıtımın sayfayı bozmasını önleyen ilk emniyet kilididir.

**Bekleme** kurulumu biten sürümün, eski sürümün denetlediği sayfalar kapanana kadar
beklemesidir. Gerekçesi, bir sayfanın ömrü boyunca tek bir sürümle konuşmasıdır: sayfa
eski sürümün önbelleğine göre yüklenmişken isteklerinin yeni sürüm tarafından
karşılanması, sürüm karışması üretir.

**Etkinleşme** eski sürümün bıraktığı artıkları temizlemek için doğru yerdir; o noktada
eski sürüm hiçbir sayfayı denetlemiyordur.

```js
// calisan-yasam-dongusu.mjs — kurulum, bekleme ve etkinlesme durumlarinin modeli
const izleme = [];

function kayitDefteri() {
  return { etkin: null, bekleyen: null, kurulan: null, denetlenen: new Set() };
}

// Yeni surum kaydedilir: kurulur, sonra ya bekler ya dogrudan etkinlesir.
function kaydet(kayit, surum, { beklemeyiAtla = false } = {}) {
  kayit.kurulan = surum;
  izleme.push(`${surum}: kuruluyor`);
  kayit.kurulan = null;
  const denetlenenVar = kayit.denetlenen.size > 0;
  if (kayit.etkin && denetlenenVar && !beklemeyiAtla) {
    kayit.bekleyen = surum;
    izleme.push(`${surum}: kuruldu → bekliyor (${kayit.etkin} hala ${kayit.denetlenen.size} sayfayi denetliyor)`);
  } else {
    etkinlestir(kayit, surum);
  }
}

function etkinlestir(kayit, surum) {
  if (kayit.etkin) izleme.push(`${kayit.etkin}: artik gecersiz`);
  kayit.etkin = surum;
  kayit.bekleyen = null;
  izleme.push(`${surum}: etkinlesiyor → etkin`);
}

// Denetim yalnizca gezinme aninda kurulur; sahiplenme cagrisi bunu beklemez.
function sayfaAc(kayit, sayfa) {
  kayit.denetlenen.add(sayfa);
  izleme.push(`${sayfa} acildi → denetleyen: ${kayit.etkin ?? "(yok)"}`);
}

function sayfaKapat(kayit, sayfa) {
  kayit.denetlenen.delete(sayfa);
  izleme.push(`${sayfa} kapandi → denetlenen sayfa: ${kayit.denetlenen.size}`);
  if (kayit.bekleyen && kayit.denetlenen.size === 0) etkinlestir(kayit, kayit.bekleyen);
}

console.log("A. Bekleme durumu: eski surum sayfalari birakana kadar bekler");
const a = kayitDefteri();
kaydet(a, "s1");
sayfaAc(a, "olcumler");
kaydet(a, "s2");
sayfaAc(a, "gunluk");
sayfaKapat(a, "olcumler");
sayfaKapat(a, "gunluk");
console.log(izleme.map((s) => "  " + s).join("\n"));

izleme.length = 0;
console.log("\nB. Beklemeyi atlama: yeni surum acik sayfalari devralir");
const b = kayitDefteri();
kaydet(b, "s1");
sayfaAc(b, "olcumler");
kaydet(b, "s2", { beklemeyiAtla: true });
console.log(izleme.map((s) => "  " + s).join("\n"));
console.log(`  → 'olcumler' sayfasi s1 ile yuklendi, isteklerini artik ${b.etkin} karsiliyor`);
```

```
A. Bekleme durumu: eski surum sayfalari birakana kadar bekler
  s1: kuruluyor
  s1: etkinlesiyor → etkin
  olcumler acildi → denetleyen: s1
  s2: kuruluyor
  s2: kuruldu → bekliyor (s1 hala 1 sayfayi denetliyor)
  gunluk acildi → denetleyen: s1
  olcumler kapandi → denetlenen sayfa: 1
  gunluk kapandi → denetlenen sayfa: 0
  s1: artik gecersiz
  s2: etkinlesiyor → etkin

B. Beklemeyi atlama: yeni surum acik sayfalari devralir
  s1: kuruluyor
  s1: etkinlesiyor → etkin
  olcumler acildi → denetleyen: s1
  s2: kuruluyor
  s1: artik gecersiz
  s2: etkinlesiyor → etkin
  → 'olcumler' sayfasi s1 ile yuklendi, isteklerini artik s2 karsiliyor
```

Birinci bölümün altıncı satırı sık gözden kaçan davranıştır: `s2` beklerken açılan yeni
sayfa da `s1` tarafından denetlenir. Bekleme durumunu bitiren şey sekmenin yenilenmesi
değil, kökene ait **bütün** sayfaların kapanmasıdır. Sekmeyi yenilemek eski sayfayı
kapatmadan yenisini açtığı için beklemeyi çoğu zaman bitirmez.

İkinci bölüm beklemeyi atlamanın ne yaptığını ve neyi riske attığını gösterir. Yeni sürüm
hemen etkinleşir ve açık sayfaları devralır. Kazanç dağıtımın anında yayılmasıdır; bedel,
eski sürümle yüklenmiş bir sayfanın isteklerinin yeni sürüm tarafından karşılanmasıdır.
Sayfanın kodu ile çalışanın beklentisi ayrıştığında bu kırılma üretir. Beklemeyi atlamak,
sürümler arası uyumun sağlandığı bilinen dağıtımlarda güvenlidir; her dağıtımda uygulanan
varsayılan bir alışkanlık değildir.

Kaydın güncellenmesi ayrı bir denetimdir: tarayıcı çalışan betiğini belirli aralıklarla
yeniden ister ve gelen içerik öncekinden **bayt düzeyinde** farklıysa yeni sürüm sayar. Bu
yüzden çalışan betiğinin kendisi uzun ömürlü olarak önbelleklenmemelidir; aksi hâlde
güncelleme denetimi eski kopyayı okur ve dağıtım hiç görülmez.

#### İstekleri Araya Girerek Karşılamak

Etkin bir çalışan, denetlediği sayfaların ürettiği her ağ isteği için bir olay alır: sayfa
gezinmeleri, betikler, biçem dosyaları, görseller, veri istekleri. Olay nesnesi isteği
taşır; çalışan iki şeyden birini yapar.

**Yanıt vermezse** istek olağan yoluna devam eder ve ağa gider. Bu, hiçbir şey yapmamanın
güvenli varsayılan olduğu anlamına gelir.

**Yanıt verirse** ağ hiç kullanılmayabilir. Yanıt bir söz olarak bildirilir; söz çözülene
kadar istek bekler. Sözleşmenin katı bir kısıtı vardır: yanıt verme kararı olay
geri çağrısı çalışırken **eşzamanlı** olarak bildirilmelidir. Karar bir söz beklendikten
sonra verilirse geç kalınmış olur; tarayıcı bu arada isteği ağa yollamıştır. Yanıtı üretmek
eşzamansız olabilir, yanıt vereceğini bildirmek olamaz.

İki ayrıntı sık atlanır. Birincisi, yanıt gövdesi **bir kez okunur**; hem önbelleğe yazıp
hem sayfaya verilmesi gerekiyorsa kopyalanmalıdır. İkincisi, başka bir kökenden gelen ve
başlıkları paylaşıma kapalı olan yanıtlar **saydam değildir**: durum kodu okunamaz, içeriği
incelenemez, boyutu belirsizdir. Böyle bir yanıtın başarılı olup olmadığı anlaşılamadığı
için önbelleğe yazılması, hata sayfasının kalıcılaşmasına yol açabilir.

Çalışanın göremediği istekler de vardır: kapsam dışındaki sayfaların istekleri, başka
kökenlerin sayfalarından doğan istekler ve çalışanın kendi betiğinin güncelleme isteği.

#### Dağıtımın Geri Alınamazlığı

Servis çalışanı, sunucuya değil kullanıcının aygıtına dağıtılan bir vekildir. Bozuk bir
sürüm sunucudan silinmekle ortadan kalkmaz; kullanıcının aygıtında etkin kalır ve sayfayı
kendi bildiği hâline sabitler. Sunucudaki düzeltme, çalışan güncelleme denetimini yapıp
yeni betiği kabul edene kadar kullanıcıya ulaşmaz.

Bunun karşılığı iki alışkanlıktır. Birincisi, çalışan betiğinin kısa ömürlü
önbelleklenmesi — güncelleme denetiminin gerçekten ağa çıkması. İkincisi, kaydı silen ve
önbellekleri boşaltan **asgari bir sürümün** hazır tutulması: ne yaptığı belirsiz bir
çalışanı devreden çıkarmanın tek yolu, onun yerine kendini kaldıran bir çalışan
dağıtmaktır.

#### Özet

- Servis çalışanı sayfaya değil kökene bağlıdır, olaylar arasında sonlandırılabilir ve
  genel kapsamında kalıcı durum tutamaz.
- Kapsam iki kuralla belirlenir: betiğin dizininden geniş olamaz ve bir sayfayı öneki
  eşleşen kayıtlardan en uzunu denetler.
- Kurulum başarısız olursa sürüm etkinleşmez; bekleme durumu, bir sayfanın ömrü boyunca
  tek sürümle konuşmasını sağlar ve kökenin bütün sayfaları kapanana kadar sürer.
- Beklemeyi atlamak dağıtımı anında yayar, ama eski sayfayı yeni sürümle eşleştirir; sürüm
  uyumu bilinmeden yapılmaz.
- İstek karşılama kararı eşzamanlı bildirilmelidir; yanıt gövdesi bir kez okunur ve saydam
  olmayan yanıtların başarısı denetlenemez.
- Çalışan kullanıcının aygıtına dağıtılan bir vekildir; bozuk sürümü geri almanın yolu,
  kendini kaldıran bir sürüm dağıtmaktır.

#### Sonraki Adım

Bu ders araya girme yetkisini kurdu ama onunla ne yapılacağını açık bıraktı. İsteği
karşılamanın tek bir doğru biçimi yoktur: istasyonun logosu ile ölçüm listesi aynı kuralla
sunulamaz. Biri değişmez ve ağdan sorulması gereksizdir; diğeri dakikada bir değişir ve
eski kopyası yanıltıcıdır. Aradaki seçim, hız ile tazelik arasında bilinçli bir ödünleşimdir
ve her kaynak türü için ayrı verilir. Sonraki ders bu kararı, önbellek anahtarının nasıl
üretildiği ve bir kopyanın ne zaman taze sayıldığı sorularıyla birlikte ele alır.

### Önbellek Stratejileri

Source: https://academia.sh/tr/kurslar/tarayici-platformu/onbellek-stratejileri

Tarayıcıdaki iki önbellek katmanının iş bölümü, başlıklarla verilen tazelik kararı ve koşullu istek, önbellek anahtarının üretimi, dört stratejinin aynı durumlarda verdiği sonuç ve sürümleme ile temizlik.

Önceki ders araya girme yetkisini kurdu, onunla ne yapılacağını açık bıraktı. İsteği
karşılamanın tek bir doğru biçimi yoktur: istasyonun logosu ile ölçüm listesi aynı kuralla
sunulamaz. Biri değişmez ve ağdan sorulması gereksizdir; diğeri dakikada bir değişir ve
eski kopyası yanıltıcıdır.

Bu ders, kaynak başına verilen bu kararı iki soruya indirir: bir kopya nereden bulunur ve
ne zaman taze sayılır.

#### İki Ayrı Önbellek Katmanı

Tarayıcıda iki ayrı önbellek vardır ve karıştırılmaları yanlış tanılara yol açar.

**HTTP önbelleği**, tarayıcının kendi yönettiği ve program tarafından görülmeyen
katmandır. Neyin ne kadar saklanacağını sunucunun gönderdiği başlıklar belirler; sayfa
kodunun sözü geçmez. Bir kaydı silmenin ya da içeriğini okumanın program yolu yoktur.

**Program denetimli önbellek**, servis çalışanının açıkça yazdığı ve sildiği ad–kayıt
deposudur. Ne saklanacağına, ne kadar tutulacağına ve ne zaman sunulacağına yazan kod
karar verir; başlıklar bu katmanda kendiliğinden hiçbir şey yapmaz.

İkisi bir zincirde durur. Servis çalışanı ağa çıktığında istek HTTP önbelleğinden
geçer. Bu, ikili önbellekleme üretir: aynı kaynak iki katmanda da saklanabilir ve program
katmanına yazılan kopya, HTTP katmanının eski kopyası olabilir. Kaçınmanın yolu, kritik
kaynakları önbelleğe alırken isteği HTTP katmanını atlayacak biçimde kurmaktır.

#### Tazelik Kararı Nerede Verilir

HTTP katmanının kararı üç başlığa dayanır: saklama izni, ömür ve doğrulayıcı. Bunlar bir
yanıtın başlıklarında okunabilir.

```js
// sunucu.mjs — uc kaynagi uc ayri tazelik politikasiyla sunan yerel sunucu
import { createServer } from "node:http";

const KAYNAKLAR = {
  "/logo.svg": {
    tip: "image/svg+xml",
    etiket: '"logo-3f2a"',
    denetim: "public, max-age=31536000, immutable",
    govde: '<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8"></svg>',
  },
  "/olcumler.json": {
    tip: "application/json",
    etiket: '"olcum-7"',
    denetim: "no-cache",
    govde: '{"sicaklik":-4.2,"nem":72}',
  },
  "/oturum": {
    tip: "text/plain; charset=utf-8",
    etiket: null,
    denetim: "no-store",
    govde: "kisiye ozel icerik",
  },
};

createServer((istek, yanit) => {
  yanit.sendDate = false; // ciktiyi belirlenimci kilmak icin: Date basligi yazilmaz
  const kaynak = KAYNAKLAR[istek.url];
  if (!kaynak) { yanit.writeHead(404).end(); return; }

  yanit.setHeader("Cache-Control", kaynak.denetim);
  if (kaynak.etiket) yanit.setHeader("ETag", kaynak.etiket);

  // Kosullu istek: istemcinin elindeki etiket sunucudakiyle ayniysa govde gonderilmez.
  if (kaynak.etiket && istek.headers["if-none-match"] === kaynak.etiket) {
    yanit.writeHead(304).end();
    return;
  }
  yanit.setHeader("Content-Type", kaynak.tip);
  yanit.setHeader("Content-Length", Buffer.byteLength(kaynak.govde));
  yanit.writeHead(200).end(kaynak.govde);
}).listen(8137, "127.0.0.1", () => console.log("dinleniyor: 127.0.0.1:8137"));
```

Aşağıdaki betik bu dosyayla aynı dizinde çalıştırılır. Bağlantı noktası olarak seçilen
8137 keyfîdir ve boş olmalıdır; kullanımdaysa iki dosyada da değiştirilir.

```bash
#!/usr/bin/env bash
# sunucu.mjs'yi baslatir, uc kaynagin yanit basliklarini okur, sonra durdurur.
node sunucu.mjs > /dev/null &
sunucu=$!
sleep 1

echo "--- /logo.svg (surumlenmis varlik) ---"
curl -sS -D - -o /dev/null http://127.0.0.1:8137/logo.svg

echo "--- /olcumler.json (her kullanimda dogrulanir) ---"
curl -sS -D - -o /dev/null http://127.0.0.1:8137/olcumler.json

echo '--- /olcumler.json + If-None-Match: "olcum-7" ---'
curl -sS -D - -o /dev/null -H 'If-None-Match: "olcum-7"' http://127.0.0.1:8137/olcumler.json

echo "--- /oturum (hic saklanmaz) ---"
curl -sS -D - -o /dev/null http://127.0.0.1:8137/oturum

kill "$sunucu"
```

```
--- /logo.svg (surumlenmis varlik) ---
HTTP/1.1 200 OK
Cache-Control: public, max-age=31536000, immutable
ETag: "logo-3f2a"
Content-Type: image/svg+xml
Content-Length: 67
Connection: keep-alive
Keep-Alive: timeout=5

--- /olcumler.json (her kullanimda dogrulanir) ---
HTTP/1.1 200 OK
Cache-Control: no-cache
ETag: "olcum-7"
Content-Type: application/json
Content-Length: 26
Connection: keep-alive
Keep-Alive: timeout=5

--- /olcumler.json + If-None-Match: "olcum-7" ---
HTTP/1.1 304 Not Modified
Cache-Control: no-cache
ETag: "olcum-7"
Connection: keep-alive
Keep-Alive: timeout=5

--- /oturum (hic saklanmaz) ---
HTTP/1.1 200 OK
Cache-Control: no-store
Content-Type: text/plain; charset=utf-8
Content-Length: 18
Connection: keep-alive
Keep-Alive: timeout=5
```

Bağlantıya ilişkin iki başlık taşıyıcı katmandan gelir ve tazelikle ilgisi yoktur; `Date`
başlığı çıktının her çalıştırmada aynı olması için bilerek kapatılmıştır, gerçek bir
sunucuda bulunur.

Üç politika üç ayrı kaynak sınıfını temsil eder. Adı sürümlenmiş bir varlık uzun ömür
alır ve içeriği hiç değişmeyeceği bildirilir; böyle bir kaynak için doğrulama isteği bile
gereksizdir. Sık değişen veri saklanabilir ama **her kullanımda doğrulanmalıdır** —
başlığın adı yanıltıcıdır; saklamayı değil doğrulamasız kullanmayı yasaklar. Kişiye özel
içerik hiçbir katmanda saklanmaz.

Üçüncü isteğin yanıtı doğrulamanın kazancını gösterir: istemci elindeki doğrulayıcıyı
gönderir, sunucu içerik değişmediğini gövdesiz bir yanıtla bildirir. Ağ turu yapılmıştır
ama gövde aktarılmamıştır. Bu, gecikmeyi ortadan kaldırmaz, bant genişliğini kurtarır;
kopan bir bağlantıda doğrulama da yapılamayacağı için tek başına çevrimdışı davranış
sağlamaz.

#### Önbellek Anahtarı

Program denetimli önbellekte bir kaydın anahtarı bir istektir ve eşleme öntanımlı olarak
**adresin tamamı** üzerinden yapılır. Bunun üç sonucu vardır.

Birincisi, sorgu dizgisi anahtarın parçasıdır. `/olcumler?tur=sicaklik` ile
`/olcumler?tur=nem` iki ayrı kayıttır. Aynı kaynağa yönlendirdiği bilinen sorgu
parametreleri varsa eşlemede sorgunun göz ardı edilmesi istenebilir; bu, izleme
parametreleri yüzünden aynı sayfanın onlarca kez saklanmasını önler.

İkincisi, aynı adresin farklı yöntemlerle yapılan istekleri ayrılabilir; önbellek
öntanımlı olarak yalnızca güvenli yöntemleri saklamaya elverişlidir. Gövdeli bir isteğin
yanıtını adres anahtarıyla saklamak, farklı gövdelerin aynı kayda düşmesi demektir.

Üçüncüsü, sunucu bir yanıtın hangi istek başlığına göre değiştiğini bildirebilir. Dil
tercihine ya da kabul edilen içerik biçimine göre farklı yanıt veren bir uç için bu
bildirim yapılmazsa, bir kullanıcının aldığı yanıt diğerine sunulur. Eşleme sırasında bu
bildirimin dikkate alınıp alınmayacağı da seçilebilir.

Anahtar üretimini kendi eline alan bir çalışan, adresi yazmadan önce normalleştirir:
izleme parametrelerini atar, kalan parametreleri sıraya koyar, adres parçasını siler.
Aynı içeriğin tek anahtara düşmesi, hem yeri hem de isabet oranını iyileştirir.

#### Dört Strateji ve Verdikleri Söz

İsteği karşılamanın dört temel biçimi vardır. Aralarındaki fark, aynı üç durumda ne
sundukları ve ağı ne zaman kullandıklarıyla görünür hâle gelir.

```js
// strateji.mjs — dort onbellek stratejisinin ayni uc durumda verdigi sonuc
const ADRES = "/istasyon/olcumler.json";

function kur({ onbelleklenmis, cevrimici }) {
  const durum = { agCagrisi: 0, onbellek: new Map() };
  if (onbelleklenmis) durum.onbellek.set(ADRES, "v1");
  durum.ag = async () => {
    durum.agCagrisi += 1;
    if (!cevrimici) throw new Error("ag yok");
    return "v2";
  };
  return durum;
}

async function onbellekOnce(d) {
  const kayit = d.onbellek.get(ADRES);
  if (kayit) return kayit;
  const yanit = await d.ag();
  d.onbellek.set(ADRES, yanit);
  return yanit;
}

async function agOnce(d) {
  try {
    const yanit = await d.ag();
    d.onbellek.set(ADRES, yanit);
    return yanit;
  } catch {
    return d.onbellek.get(ADRES) ?? "(yedek sayfa)";
  }
}

async function yenidenDogrulayarakSun(d) {
  const kayit = d.onbellek.get(ADRES);
  const tazeleme = d.ag().then((yanit) => d.onbellek.set(ADRES, yanit)).catch(() => {});
  if (kayit) return kayit;          // bekletmeden eski kopya verilir
  await tazeleme;
  return d.onbellek.get(ADRES) ?? "(yedek sayfa)";
}

async function yalnizAg(d) {
  try { return await d.ag(); } catch { return "(yedek sayfa)"; }
}

const stratejiler = [
  ["onbellek once", onbellekOnce],
  ["ag once", agOnce],
  ["yeniden dogrulayarak sun", yenidenDogrulayarakSun],
  ["yalniz ag", yalnizAg],
];

const durumlar = [
  ["onbellek bos, cevrimici", { onbelleklenmis: false, cevrimici: true }],
  ["onbellekte v1, cevrimici", { onbelleklenmis: true, cevrimici: true }],
  ["onbellekte v1, cevrimdisi", { onbelleklenmis: true, cevrimici: false }],
];

console.log("strateji".padEnd(26) + "durum".padEnd(28) + "sunulan".padEnd(16) + "ag  onbellekte kalan");
for (const [stratejiAdi, calistir] of stratejiler) {
  for (const [durumAdi, secenek] of durumlar) {
    const d = kur(secenek);
    const sunulan = await calistir(d);
    await new Promise((c) => setImmediate(c)); // arka plandaki tazeleme bitsin
    console.log(
      stratejiAdi.padEnd(26) + durumAdi.padEnd(28) +
      String(sunulan).padEnd(16) + String(d.agCagrisi).padEnd(4) +
      (d.onbellek.get(ADRES) ?? "(bos)"));
  }
}
```

```
strateji                  durum                       sunulan         ag  onbellekte kalan
onbellek once             onbellek bos, cevrimici     v2              1   v2
onbellek once             onbellekte v1, cevrimici    v1              0   v1
onbellek once             onbellekte v1, cevrimdisi   v1              0   v1
ag once                   onbellek bos, cevrimici     v2              1   v2
ag once                   onbellekte v1, cevrimici    v2              1   v2
ag once                   onbellekte v1, cevrimdisi   v1              1   v1
yeniden dogrulayarak sun  onbellek bos, cevrimici     v2              1   v2
yeniden dogrulayarak sun  onbellekte v1, cevrimici    v1              1   v2
yeniden dogrulayarak sun  onbellekte v1, cevrimdisi   v1              1   v1
yalniz ag                 onbellek bos, cevrimici     v2              1   (bos)
yalniz ag                 onbellekte v1, cevrimici    v2              1   v1
yalniz ag                 onbellekte v1, cevrimdisi   (yedek sayfa)   1   v1
```

İkinci satır **önbellek önce** stratejisinin belirleyici özelliğidir: kopya varken ağ hiç
kullanılmaz. Yanıt en hızlı biçimde gelir ve bağlantıdan bağımsızdır; bedeli, kaynak
sunucuda değişse bile eski kopyanın süresiz sunulmasıdır. Bu yüzden yalnızca adı
sürümlenmiş, içeriği asla değişmeyen varlıklar için doğrudur.

Beşinci ve altıncı satır **ağ önce** stratejisini özetler: çevrimiçiyken her zaman
güncel, çevrimdışıyken elde ne varsa. Ağın yavaş olduğu durumda kullanıcı ağın gecikmesi
kadar bekler; gerçek dağıtımlarda bu bekleme bir zaman aşımıyla sınırlanır ve süre dolunca
kopyaya düşülür.

Sekizinci satır **yeniden doğrulayarak sunma** stratejisinin ödünleşimini tek satırda
gösterir: sunulan `v1`, önbellekte kalan `v2`. Kullanıcı beklemeden yanıt alır ama bir
sürüm geriden gelir; güncel içerik bir sonraki ziyarette görünür. Bu, tazeliğin bir tur
gecikmesine razı olunabilen içerik için doğru seçimdir ve arayüzün bunu üstü kapalı
bırakmaması gerekir.

Son üç satır **yalnız ağ** stratejisinin önbelleği hiç yazmadığını gösterir: üçüncü
durumda önbellekte `v1` durduğu hâlde yedek sayfa sunulmuştur. Ölçüm gönderimi gibi
biriktirilemeyen istekler bu stratejiye aittir; çevrimdışıyken yapılacak şey eski veriyi
göstermek değil, isteğin gerçekleşmediğini bildirmektir.

#### Sürümleme, Temizlik ve Kota

Önbellekler adlandırılır ve ad sürüm taşır. Yeni bir çalışan sürümü kurulurken kendi adına
yazar; etkinleşirken kendi adı dışındaki bütün önbellekleri siler. Bu iki adım birlikte,
eski ve yeni varlıkların karışmasını önler: bir sürümün varlıkları ya tümüyle vardır ya
tümüyle yoktur.

Kurulum sırasında toplanan kaynak listesi kısa tutulur. Uygulamanın açılması için gereken
en küçük küme — belge kabuğu, temel biçem, temel betik, çevrimdışı yedek sayfa — kuruluma
girer; geri kalan varlıklar kullanıldıkça yazılır. Kurulumun bir bütün olarak başarısız
sayılması, uzun bir listenin tek bir kalemi yüzünden bütün dağıtımı engellemesi anlamına
gelir.

Depolanan verinin sınırsız olmadığı da hesaba katılır. Tarayıcı köken başına bir üst sınır
uygular ve yer daraldığında kökenin verisini bütün olarak silebilir. Buradan iki kural
çıkar: önbellek kalıcı bir veritabanı gibi kullanılmaz ve önbellekten okunan bir kaydın
bulunamama olasılığı her zaman ele alınır.

#### Özet

- Tarayıcıda iki önbellek katmanı vardır: başlıklarla yönetilen HTTP önbelleği ve kodla
  yönetilen program denetimli önbellek; ikisi zincirde arka arkaya durur.
- Tazelik başlıkları üç kararı bildirir: saklanabilir mi, ne kadar taze sayılır, hangi
  doğrulayıcıyla sınanır. Doğrulama gövdesiz bir yanıtla sonuçlanır ve bant genişliğini
  kurtarır, gecikmeyi kaldırmaz.
- Önbellek anahtarı öntanımlı olarak adresin tamamıdır; sorgu dizgisi anahtarın
  parçasıdır ve normalleştirilmediğinde aynı içerik birden çok kayda düşer.
- Önbellek önce en hızlı ve en eskiye açık, ağ önce en taze ve en yavaş, yeniden
  doğrulayarak sunma hızlı ama bir sürüm geriden, yalnız ağ ise çevrimdışı davranışı
  olmayan stratejidir.
- Önbellek adları sürüm taşır; kurulumda yazılır ve etkinleşmede eski adlar silinir. Kurulum
  listesi kısa tutulur ve depolama sınırlıdır.

#### Sonraki Adım

Bu ders sayfayı ağdan bağımsızlaştırdı: kaynaklar yerelde, kararlar tanımlı. Ortaya çıkan
şey artık bir sayfadan fazlasıdır ve kullanıcıya da öyle görünmesi gerekir. Kuzey Yamaç
sayfasını sahada kullanan kişi onu her seferinde adres yazarak açmak istemez; kendi
penceresinde, kendi simgesiyle, tarayıcı arayüzü olmadan açılmasını bekler. Bunun için
sayfanın kendini tanıtan bir bildirim dosyası, kurulabilirliğin ölçütleri ve arka plandan
gelen bildirimlere ilişkin bir izin modeli gerekir. Sonraki ders bu üç parçayı ele alır.

### Aşamalı Web Uygulamaları

Source: https://academia.sh/tr/kurslar/tarayici-platformu/asamali-web-uygulamalari

Sayfayı kurulabilir bir uygulamaya dönüştüren parçalar; uygulama bildiriminin alanları ve iç tutarlılığı, kurulabilirlik ölçütleri, pencere kipinin getirdiği sorumluluklar, izin modeli ve çevrimdışı gönderim kuyruğu.

Önceki ders sayfayı ağdan bağımsızlaştırdı: kaynaklar yerelde, kararlar tanımlı. Ortaya
çıkan şey bir sayfadan fazlasıdır ve kullanıcıya da öyle görünmesi gerekir. Kuzey Yamaç
sayfasını sahada kullanan kişi onu her seferinde adres yazarak açmak istemez; kendi
penceresinde, kendi simgesiyle açılmasını bekler.

Bu ders üç parçayı ele alır: sayfanın kendini tanıttığı bildirim, kurulabilirliğin
koşulları ve arka plandan gelen bildirimlerin izin modeli. Bunlara **aşamalı web
uygulaması (progressive web app)** yaklaşımı denir; adındaki aşamalılık, sayfanın
desteklenmeyen ortamda sıradan bir sayfa olarak çalışmayı sürdürmesini anlatır.

#### Uygulama Bildirimi

Sayfa, kendini bir uygulama olarak tanıtan bir JSON dosyasına bağlanır. Dosya, işletim
sisteminin uygulamayı listelemek ve pencereyi açmak için ihtiyaç duyduğu bilgileri taşır:
görünen ad, kısa ad, açılışta gidilecek adres, uygulamanın sayılacağı yol kapsamı, pencere
kipi, simgeler ve renkler.

Alanların çoğu biçimsel olarak bağımsızdır, ama aralarında sessiz tutarlılık koşulları
vardır. Bunlar denetlenebilir.

```js
// bildirim-denetimi.mjs — uygulama bildiriminin ic tutarliligini sinar
const ZORUNLU = ["name", "start_url", "icons", "display"];
const GEREKEN_BOYUTLAR = [192, 512];

function denetle(bildirim) {
  const bulgular = [];

  for (const alan of ZORUNLU)
    if (!(alan in bildirim)) bulgular.push(`eksik alan: ${alan}`);

  const kapsam = bildirim.scope ?? "/";
  if (bildirim.start_url && !bildirim.start_url.startsWith(kapsam))
    bulgular.push(`start_url (${bildirim.start_url}) kapsamin (${kapsam}) disinda`);

  const simgeler = bildirim.icons ?? [];
  const boyutlar = new Set(
    simgeler.flatMap((s) => (s.sizes ?? "").split(" ")).map((b) => Number(b.split("x")[0])));
  for (const gereken of GEREKEN_BOYUTLAR)
    if (![...boyutlar].some((b) => b >= gereken))
      bulgular.push(`${gereken}x${gereken} ve uzeri simge yok`);

  if (!simgeler.some((s) => (s.purpose ?? "").split(" ").includes("maskable")))
    bulgular.push("maskable amacli simge yok: kirpilan yuvalarda kenarlar kesilir");

  if (bildirim.display && !["fullscreen", "standalone", "minimal-ui", "browser"].includes(bildirim.display))
    bulgular.push(`taninmayan display degeri: ${bildirim.display}`);

  return bulgular;
}

const bildirimler = {
  "kuzey-yamac.webmanifest": {
    name: "Kuzey Yamaç Ölçüm İstasyonu",
    short_name: "Kuzey Yamaç",
    start_url: "/istasyon/kuzey-yamac/",
    scope: "/istasyon/kuzey-yamac/",
    display: "standalone",
    background_color: "#0f1115",
    theme_color: "#0f1115",
    icons: [
      { src: "/simge-192.png", sizes: "192x192", type: "image/png" },
      { src: "/simge-512.png", sizes: "512x512", type: "image/png" },
      { src: "/simge-maskeli.png", sizes: "512x512", type: "image/png", purpose: "maskable" },
    ],
  },
  "eksik.webmanifest": {
    short_name: "Kuzey Yamaç",
    start_url: "/panel/",
    scope: "/istasyon/",
    display: "app",
    icons: [{ src: "/simge-64.png", sizes: "64x64", type: "image/png" }],
  },
};

for (const [ad, bildirim] of Object.entries(bildirimler)) {
  const bulgular = denetle(bildirim);
  console.log(`${ad}: ${bulgular.length === 0 ? "tutarli" : bulgular.length + " bulgu"}`);
  for (const b of bulgular) console.log("  - " + b);
}
```

```
kuzey-yamac.webmanifest: tutarli
eksik.webmanifest: 6 bulgu
  - eksik alan: name
  - start_url (/panel/) kapsamin (/istasyon/) disinda
  - 192x192 ve uzeri simge yok
  - 512x512 ve uzeri simge yok
  - maskable amacli simge yok: kirpilan yuvalarda kenarlar kesilir
  - taninmayan display degeri: app
```

İkinci bulgu en sinsi olanıdır. Bildirimdeki kapsam, uygulamanın sınırını çizer: kapsam
dışındaki bir adres uygulama penceresinde değil tarayıcıda açılır. Açılış adresi kapsam
dışındaysa uygulama kurulur, simgesi görünür, ama her açılışta kendi penceresinden çıkar.
Bu, servis çalışanının kapsamıyla da hizalanmalıdır; iki kapsam ayrıştığında uygulama
penceresinde açılan bir adres çevrimdışı desteğinden yoksun kalabilir.

Maskeli simge bulgusu görsel bir ayrıntı gibi görünür ve değildir. Sistem simgeyi kendi
biçimine kırpar; kırpmayı hesaba katmayan bir simgenin kenarları kesilir. Ayrı bir amaç
etiketiyle işaretlenen simge, çevresinde güvenli bir boşluk bırakılmış olanıdır.

#### Kurulabilirliğin Ölçütleri

Kurulabilirlik bildirimin varlığıyla kendiliğinden gelmez. Ortak çekirdek şunlardır:
sayfanın güvenli bir kökenden sunulması, geçerli bir bildirimin bağlanmış olması, yeterli
boyutta simge sunulması ve genellikle istekleri karşılayan bir servis çalışanının kayıtlı
olması. Bunların ötesindeki ölçütler tarayıcıdan tarayıcıya değişir ve zamanla değişebilir;
tek doğru yaklaşım, kurulum akışını **özellik saptamayla** kurmaktır.

Kurulum önerisinin ne zaman gösterileceği sayfanın kararı değildir. Tarayıcı uygun gördüğü
anda bir olay yayar; sayfa bu olayı yakalayıp erteleyebilir ve kendi arayüzündeki bir
düğmeye bağlayabilir. Kurulum penceresini kullanıcı etkileşimi olmadan açmak mümkün
değildir. Olay hiç gelmeyebilir de: uygulama zaten kuruluysa ya da ortam kurulumu
desteklemiyorsa. Kurulum düğmesi bu yüzden koşullu gösterilir.

#### Pencere Kipi ve Getirdiği Sorumluluk

Bağımsız pencere kipinde tarayıcı arayüzü görünmez: adres çubuğu, geri düğmesi, yenileme
düğmesi yoktur. Sayfanın gizli olarak dayandığı her tarayıcı yeteneği artık sayfanın kendi
sorumluluğudur.

Üç şey doğrudan etkilenir. **Geri gezinme** arayüzde açıkça sunulmalıdır; kullanıcı geri
dönemediği bir ekranda sıkışır. **Yenileme** yolu bırakılmalıdır; çevrimdışı yedek sayfa
gösterildiğinde kullanıcının yeniden denemek için bir düğmesi olmalıdır. **Adresin
paylaşılması** için bir yol gerekir; adres çubuğu olmadığında bulunulan ekranın adresini
kopyalamanın başka yolu yoktur.

Sayfanın hangi kipte açıldığı bir ortam sorgusuyla okunabilir; bu, aynı belgenin tarayıcı
sekmesinde ve uygulama penceresinde farklı gezinme ögeleri göstermesini sağlar. Kip
bildirimi ayrıca bir yedek zinciri gibi çalışır: istenen kip desteklenmiyorsa daha az
ayrıcalıklı kipe düşülür, en sonunda sıradan sekme kalır.

#### Bildirimler ve İzin Modeli

Bildirim gönderme yetkisi kullanıcının açık onayına bağlıdır ve izin üç durumdan birinde
bulunur: sorulmamış, verilmiş, reddedilmiş. Reddedilmiş durum kalıcıdır; sayfa aynı izni
yeniden soramaz, kullanıcı ayarlardan geri almadıkça.

Bu kalıcılık tek bir kural doğurur: **izin, kullanıcının o izne ihtiyaç duyan bir eylemi
başlattığı anda istenir.** Sayfa açılır açılmaz sorulan izin çoğunlukla reddedilir ve o
reddetme kalıcıdır; ileride gerçekten gerektiğinde sorma hakkı yakılmış olur. Doğru sıra,
önce arayüzde ne için kullanılacağını anlatan bir adım göstermek, sonra tarayıcının izin
penceresini açmaktır.

Uzaktan bildirim ayrıca bir sunucu tarafı gerektirir: sayfanın oluşturduğu abonelik
sunucuya kaydedilir, sunucu iletiyi bir aracıya gönderir, aracı da servis çalışanını
uyandırır. Abonelik kalıcı değildir; tarayıcı yenileyebilir ve sunucudaki kayıt eskiyebilir.
Bu yüzden bildirim hiçbir zaman tek iletişim kanalı sayılmaz; uygulama açıldığında aynı
bilgi arayüzde de bulunmalıdır.

#### Çevrimdışı Gönderim ve Yinelemeye Dayanıklılık

Çevrimdışı okuma önceki dersin konusuydu; çevrimdışı **yazma** ayrı bir sorundur. Sahada
alınan ölçüm bağlantı yokken gönderilemez; kaybolmaması için yerel bir kuyruğa yazılır ve
bağlantı geldiğinde boşaltılır. Bazı ortamlarda bu boşaltmayı tarayıcı, sayfa kapalıyken
bile yapabilir; desteği koşulludur ve saptanarak kullanılır.

Kuyruğun kendisi yeni bir hata sınıfı üretir. Sunucu kaydı yazdıktan sonra yanıt yolda
kaybolursa, istemci gönderimi başarısız sayar ve yeniden dener.

```js
// kuyruk.mjs — cevrimdisi gonderim kuyrugu ve yinelemeye dayaniklilik
// Senaryo: kayit sunucuya yazilir, ama yanit istemciye ulasmadan baglanti kopar.
// Istemci basarisiz saydigi gonderimi yeniden dener.

function sunucu() {
  const kayitlar = [];
  const gorulenAnahtarlar = new Set();
  return {
    kayitlar,
    yaz(govde, anahtar) {
      if (anahtar && gorulenAnahtarlar.has(anahtar)) return "yinelenen istek, yok sayildi";
      if (anahtar) gorulenAnahtarlar.add(anahtar);
      kayitlar.push(govde);
      return "yazildi";
    },
  };
}

// Kuyrugu bosaltir. yanitKaybi[i] true ise i. denemede yanit istemciye ulasmaz.
function bosalt(kuyruk, s, yanitKaybi) {
  let deneme = 0;
  while (kuyruk.length > 0 && deneme < 5) {
    const is = kuyruk[0];
    const sonuc = s.yaz(is.govde, is.anahtar);
    const kayboldu = yanitKaybi[deneme] === true;
    deneme += 1;
    console.log(`  deneme ${deneme}: sunucu "${sonuc}"` +
      (kayboldu ? " → yanit kayboldu, yeniden denenecek" : " → yanit ulasti"));
    if (!kayboldu) kuyruk.shift();
  }
}

const olcum = { istasyon: "kuzey-yamac", sicaklik: -4.2 };

for (const anahtar of [null, "gonderim-9f3c"]) {
  console.log(anahtar ? `yinelemeye dayanikli anahtar: ${anahtar}` : "anahtarsiz gonderim:");
  const s = sunucu();
  const kuyruk = [{ govde: olcum, anahtar }];
  bosalt(kuyruk, s, [true, false]); // ilk denemede yanit kaybi
  console.log(`  sunucudaki kayit sayisi: ${s.kayitlar.length}\n`);
}
```

```
anahtarsiz gonderim:
  deneme 1: sunucu "yazildi" → yanit kayboldu, yeniden denenecek
  deneme 2: sunucu "yazildi" → yanit ulasti
  sunucudaki kayit sayisi: 2

yinelemeye dayanikli anahtar: gonderim-9f3c
  deneme 1: sunucu "yazildi" → yanit kayboldu, yeniden denenecek
  deneme 2: sunucu "yinelenen istek, yok sayildi" → yanit ulasti
  sunucudaki kayit sayisi: 1
```

Aynı ölçüm iki kez kaydedilmiştir ve istemci tarafında hiçbir hata görünmemiştir. Ayrım,
gönderimle birlikte üretilen ve yeniden denemelerde **değişmeyen** bir anahtardır. Anahtar
istemcide, gönderim kuyruğa alınırken üretilir; her denemede yeniden üretilirse hiçbir işe
yaramaz. Sunucu tarafındaki karşılık, aynı anahtarı ikinci kez gören isteği yok sayıp
başarı bildirmektir.

Kuyruğun kullanıcıya görünürlüğü de tasarımın parçasıdır. Gönderilmemiş ölçümlerin sayısı
arayüzde durmalı, gönderim tamamlandığında kaybolmalıdır; kullanıcının sakladığını sandığı
bir verinin sessizce kuyrukta beklemesi, çevrimdışı desteğin en pahalı hatasıdır.

#### Özet

- Uygulama bildirimi, sayfayı işletim sistemine tanıtan JSON dosyasıdır; alanları arasında
  denetlenebilir tutarlılık koşulları vardır.
- Açılış adresi bildirimin kapsamı içinde olmalıdır; dışında kalırsa uygulama her açılışta
  kendi penceresinden çıkar.
- Kurulabilirlik ölçütleri güvenli köken, geçerli bildirim ve genellikle kayıtlı bir servis
  çalışanını içerir; ötesi ortama göre değişir ve özellik saptamayla ele alınır.
- Bağımsız pencere kipinde geri gezinme, yenileme ve adres paylaşımı sayfanın kendi
  sorumluluğuna geçer.
- İzin reddi kalıcıdır; izin, kullanıcının o yeteneğe ihtiyaç duyan bir eylemi başlattığı
  anda istenir.
- Çevrimdışı gönderim kuyruğu yeniden deneme üretir; gönderim başına değişmeyen bir anahtar
  olmadan aynı kayıt birden çok kez yazılır.

#### Sonraki Adım

Buraya kadar kurulan katmanlar — belge ağacı, olay yolu, kapsüllenmiş bileşenler, araya
giren çalışan, önbellek kararları — birlikte çalıştığında görünmez olurlar. Bir şey ters
gittiğinde ise sorunun hangi katmandan geldiği metne bakarak anlaşılmaz: rozetin rengi
yanlış olabilir çünkü bir biçem kuralı beklenmedik biçimde kazanmıştır, ya da ölçüm eski
olabilir çünkü istek önbellekten karşılanmıştır. Bu iki tanı farklı sorulara ve farklı
ölçülere ihtiyaç duyar. Sonraki konu, tarayıcının sunduğu tanı araçlarını bu sorular
üzerinden ele alır ve ilk ders en yakınından başlar: bir ögenin gerçekte hangi değerlerle
çizildiğini okumaktan.

## Geliştirici Araçları

Öge denetçisi, ağ paneli, başarım kaydı, bellek anlık görüntüsü ve kesme noktası araçlarının her birinin yanıtladığı soru ve okunma biçimi.

### Öğe ve Biçem İnceleme

Source: https://academia.sh/tr/kurslar/tarayici-platformu/oge-ve-bicem-inceleme

Öge denetçisinin yanıtladığı soru; canlı ağaç ile kaynak metin farkı, bildirilen–hesaplanan–kullanılan değer ayrımı, basamaklama sırasının okunması, kalıtımın izlenmesi ve kutu ölçülerinin yorumlanması.

Buraya kadar kurulan katmanlar birlikte çalıştığında görünmez olurlar. Bir şey ters
gittiğinde ise sorunun hangi katmandan geldiği kaynağa bakarak anlaşılmaz: ölçüm rozetinin
rengi yanlış olabilir çünkü bir biçem kuralı beklenmedik biçimde kazanmıştır, ya da
gösterdiği sayı eski olabilir çünkü istek önbellekten karşılanmıştır.

Bu konu, tarayıcının sunduğu tanı araçlarını sordukları sorular üzerinden ele alır. Her
aracın yanıtladığı belirli bir soru, okunan belirli bir ölçü ve o ölçünün yorumlanma
biçimi vardır. İlk araç en yakınındakidir: bir ögenin gerçekte hangi değerlerle
çizildiğini gösteren **öge denetçisi**.

#### Denetçinin Gösterdiği Ağaç

Denetçideki ağaç, sunucudan gelen metnin bir görüntüsü değildir. DOM API'si dersinde
kurulan ayrım burada araca yansır: gösterilen şey, ayrıştırmanın ve o ana kadar çalışmış
bütün betiklerin sonucundaki **canlı ağaçtır**.

Aradaki farkı üreten dört kaynak vardır. Ayrıştırıcının kurtarma kuralları eksik kapanış
etiketlerini tamamlar ve yanlış yerdeki ögeleri taşır; kaynakta olmayan bir yapı ağaçta
belirir. Betikler düğüm ekler ve siler. Gölge ağaçları ayrı alt ağaçlar olarak gösterilir
ve belge ağacında bulunmazlar. Sözde ögeler ağaçta düğüm değildir, ama çizildikleri için
denetçide ayrı satırlar olarak işaretlenir.

Buradan doğrudan bir tanı kuralı çıkar: **denetçide gördüğü yapıyı kaynakta bulamayan
kişi, iki farklı şeye bakıyordur.** Sunucunun gerçekte ne gönderdiğini görmek ayrı bir
sorudur ve ağ panelinin konusudur.

#### Üç Değer: Bildirilen, Hesaplanan, Kullanılan

Biçem incelemesindeki en sık karışıklık, aynı özelliğin farklı yerlerde farklı biçimde
görünmesidir. Nedeni, bir değerin çizime kadar üç aşamadan geçmesidir.

**Bildirilen değer** biçem dosyasında yazıldığı gibidir: `1.5rem`, `50%`, `inherit`,
`auto`. Basamaklamayı kazanan bildirim bu biçimiyle listelenir.

**Hesaplanan değer**, kalıtım ve göreli birimler çözüldükten sonraki değerdir. Yazı
boyutuna göre tanımlı bir birim burada saltık uzunluğa iner; anahtar sözcükler kendi
karşılıklarına çevrilir. Kalıtım bu aşamadaki değeri aktarır.

**Kullanılan değer**, yerleşim hesabı bittikten sonra ortaya çıkan sayıdır. Yüzdeler
kapsayıcının gerçek genişliğine göre çözülür, `auto` bir sayıya iner, esneme ve ızgara
dağıtımı sonucunu burada verir.

Ayrım tanıda belirleyicidir. Genişliği yüzde olarak yazılmış bir öge beklenenden dar
çizildiğinde hesaplanan değer hâlâ yüzde gösterebilir; sorun kapsayıcının genişliğindedir
ve yalnızca kullanılan değere bakarak görülür. Tersine, bir renk bildiriminin hiç
uygulanmadığı durumda kullanılan değere bakmak yanıltır; oraya bakılması gereken yer
basamaklama sırasıdır.

#### Sıralamanın Kuralı ve Üstü Çizilenler

Biçem listesi, bir ögeye eşleşen bütün bildirimleri kazanandan kaybedene doğru sıralar ve
kaybedenlerin üstünü çizer. Üstü çizili satırlar gürültü değil, tanının kendisidir:
bir kuralın neden uygulanmadığı, onu geçen kuralın listedeki yerinden okunur.

Sıralamayı üreten hesap üç ölçüte dayanır.

```js
// basamaklama.mjs — bicem listesinin siralamasini ve kazanan bildirimi ureten hesap
// Ozgulluk uclusu: (kimlik, sinif/oznitelik/sozde-sinif, tur/sozde-oge)
function ozgulluk(secici) {
  const parcalar = secici.split(/\s+/);
  let a = 0, b = 0, c = 0;
  for (const p of parcalar) {
    a += (p.match(/#[\w-]+/g) ?? []).length;
    b += (p.match(/\.[\w-]+|\[[^\]]+\]|:(?!:)[\w-]+/g) ?? []).length;
    c += (p.match(/::[\w-]+/g) ?? []).length;
    if (/^[a-zA-Z][\w-]*/.test(p)) c += 1;
  }
  return [a, b, c];
}

// Katman agirligi: buyuk olan kazanir.
const AGIRLIK = { "tarayici": 0, "yazar": 1, "yazar!": 2, "tarayici!": 3 };
const agirlik = (b) => AGIRLIK[b.kaynak + (b.onemli ? "!" : "")];

function sirala(bildirimler) {
  return [...bildirimler].sort((x, y) => {
    if (agirlik(x) !== agirlik(y)) return agirlik(y) - agirlik(x);
    const ox = ozgulluk(x.secici), oy = ozgulluk(y.secici);
    for (let i = 0; i < 3; i++) if (ox[i] !== oy[i]) return oy[i] - ox[i];
    return y.sira - x.sira; // sonra yazilan kazanir
  });
}

// span.deger ogesine uygulanan renk bildirimleri
const bildirimler = [
  { sira: 1, kaynak: "tarayici", secici: "span", deger: "canvastext", onemli: false },
  { sira: 2, kaynak: "yazar", secici: ".deger", deger: "#333", onemli: false },
  { sira: 3, kaynak: "yazar", secici: "#pano .deger", deger: "#0a5", onemli: false },
  { sira: 4, kaynak: "yazar", secici: "span.deger", deger: "#b23", onemli: false },
  { sira: 5, kaynak: "yazar", secici: ".deger", deger: "#07c", onemli: false },
  { sira: 6, kaynak: "yazar", secici: ".uyari .deger", deger: "#c30", onemli: true },
];

console.log("color bildirimleri (kazanan en ustte):");
for (const [i, b] of sirala(bildirimler).entries()) {
  const [a, s, t] = ozgulluk(b.secici);
  console.log(
    `  ${i === 0 ? "→" : " "} ${b.secici.padEnd(16)} ${b.deger.padEnd(12)}` +
    `ozgulluk ${a}-${s}-${t}  ${b.kaynak}${b.onemli ? " !important" : ""}` +
    `${i === 0 ? "" : "   (ustu cizili)"}`);
}

// Kalitim: yalnizca hicbir bildirim eslesmediginde devreye girer.
const kazanan = (liste) => (liste.length ? sirala(liste)[0].deger : null);
const ebeveynRengi = kazanan(bildirimler);
const cocukBildirimleri = []; // strong ogesine hicbir renk kurali eslesmiyor

console.log("\nspan.deger  → hesaplanan color:", ebeveynRengi);
console.log("strong      → hesaplanan color:",
  kazanan(cocukBildirimleri) ?? `${ebeveynRengi} (kalitim)`);
```

```
color bildirimleri (kazanan en ustte):
  → .uyari .deger    #c30        ozgulluk 0-2-0  yazar !important
    #pano .deger     #0a5        ozgulluk 1-1-0  yazar   (ustu cizili)
    span.deger       #b23        ozgulluk 0-1-1  yazar   (ustu cizili)
    .deger           #07c        ozgulluk 0-1-0  yazar   (ustu cizili)
    .deger           #333        ozgulluk 0-1-0  yazar   (ustu cizili)
    span             canvastext  ozgulluk 0-0-1  tarayici   (ustu cizili)

span.deger  → hesaplanan color: #c30
strong      → hesaplanan color: #c30 (kalitim)
```

Listede üç ayrı ölçüt iş başındadır ve hangisinin devreye girdiği satırlar
karşılaştırılarak okunur.

Birinci satır **ağırlığın özgüllüğü yendiğini** gösterir: özgüllüğü daha düşük olan bildirim
yalnızca önemli işaretlendiği için en üstte durur. Bir kuralın neden kazandığı sorusuna
"daha özgül olduğu için" yanıtı verilmeden önce ağırlık sütununa bakılır.

İkinci ve üçüncü satırlar **özgüllük karşılaştırmasının basamaklı** olduğunu gösterir:
kimlik sayısı eşit değilse geri kalan hiç hesaba katılmaz. Bir kimlik, kaç sınıf yazılırsa
yazılsın onları geçer.

Dördüncü ve beşinci satırlar **kaynak sırasının** son ölçüt olduğunu gösterir: aynı
seçicinin iki bildirimi arasında sonra yazılan kazanır. Bu, aynı sınıfın iki ayrı dosyada
tanımlandığı durumlarda dosya yükleme sırasının tanıya girdiği yerdir.

Son satır tarayıcının kendi varsayılan biçeminin listede göründüğünü ve her zaman en
altta kaldığını gösterir. Yazarın hiçbir kuralı olmayan bir özellik için bu satır kazanan
olur; beklenmedik bir kenar boşluğunun kaynağı çoğunlukla buradadır.

#### Kalıtımın Okunması

Çıktının son iki satırı kalıtımın koşulunu gösterir: bir öge için hiçbir bildirim
eşleşmediğinde ve özellik kalıtılan bir özellikse, değer ebeveynin hesaplanan değerinden
alınır.

Denetçide bu, kaynağı ögenin kendi kurallarında bulunamayan bir değer olarak görünür ve
listede ait olduğu ata gösterilir. Tanı kuralı şudur: **kalıtılan bir değeri değiştirmek
için ögenin kendisine kural yazmak gerekir; ataya yazılmış kural, arada eşleşen başka bir
bildirim varsa hiç görünmez.** Kalıtım basamaklamanın bir parçası değil, basamaklama
sonuçsuz kaldığında devreye giren yedeğidir.

#### Kutu Ölçülerini Okumak

Kutu ölçüleri görünümü, ögenin içerik alanını, iç boşluğunu, kenarlığını ve dış boşluğunu
sayılarla gösterir. Buradaki bütün sayılar **kullanılan değerlerdir**: yüzdeler çözülmüş,
`auto` bir sayıya inmiş, çakışan dış boşluklar birleştirilmiştir.

Üç okuma sık gerekir. İçerik genişliğinin yazılandan farklı olması, kutu boyutlandırma
modelinin iç boşluk ile kenarlığı genişliğe dahil ettiğini gösterir. Dış boşluğun yazılanın
yarısı çıkması, komşu kutuyla çakışmanın gerçekleştiğini gösterir. Sıfır yükseklik, ögenin
akışta yer kaplamadığını — çocuklarının kayan ya da mutlak konumlu olduğunu — gösterir.

Bir ögenin hiç çizilmemesi ayrı bir durumdur ve kutu ölçülerinde değil hesaplanan
değerlerde okunur: görüntüleme türü, görünürlük, saydamlık ve kırpma özelliklerinin
hangisinin devrede olduğu ayrı ayrı sınanır. Dördü de ögeyi gözden kaldırır, ama üçü
yerini korur, biri kaldırmaz.

#### Durumları Zorlamak

Fare üzerindeyken ya da odaktayken uygulanan kurallar, incelenmek istendiğinde çoğunlukla
kaybolur: inceleme için imleç ögeden ayrılır, odak denetçiye geçer. Bu yüzden denetçiler
ögenin durumunu **zorlamaya** izin verir; öge gerçekten o durumda olmasa da ilgili sözde
sınıf kuralları uygulanır ve listede görünür.

Aynı yol erişilebilirlik denetiminde de kullanılır: odak göstergesinin gerçekten görünür
olup olmadığı, odak durumu zorlanarak sınanır. Odak Sırası ve Klavye Etkileşimi dersinde
kurulan gereklilik burada ölçülebilir hâle gelir.

Sözde ögeler ayrı satırlarda listelenir çünkü kendi kural kümeleri vardır ve ana ögenin
listesinde görünmezler. Üretilmiş içeriğin neden görünmediği sorusunun yanıtı çoğunlukla
o ayrı listede durur.

#### Özet

- Denetçideki ağaç canlı ağaçtır: ayrıştırıcı kurtarmaları, betik değişiklikleri, gölge
  ağaçları ve sözde ögeler kaynak metinden ayrışır.
- Bir değer bildirilen, hesaplanan ve kullanılan aşamalarından geçer; hangi aşamaya
  bakılacağı sorunun türüne bağlıdır.
- Biçem listesi bildirimleri ağırlık, özgüllük ve kaynak sırası ölçütleriyle sıralar; üstü
  çizili satırlar bir kuralın neden kaybettiğini söyler.
- Özgüllük karşılaştırması basamaklıdır: üstteki basamak eşit değilse alttakiler hiç hesaba
  katılmaz.
- Kutu ölçülerindeki sayılar kullanılan değerlerdir; yazılan değerden sapmaları kutu
  modeli, çakışma ve akış davranışıyla açıklanır.
- Etkileşim durumları zorlanarak incelenir; sözde ögelerin kuralları ayrı listelenir.

#### Sonraki Adım

Bu ders bir ögenin neden öyle göründüğünü yanıtladı, ama içeriğinin neden o içerik olduğunu
yanıtlamadı. Rozetteki sayı eskiyse sorun biçemde değildir: ya istek hiç yapılmamıştır, ya
önbellekten karşılanmıştır, ya sunucu eski veriyi döndürmüştür, ya da yanıt beklenenden çok
geç gelmiştir. Dördü ayrı ölçülerle ayrılır ve hepsi tek bir yerde okunur: isteğin ne zaman
başladığı, hangi aşamada ne kadar beklediği ve hangi başlıklarla döndüğü. Sonraki ders bu
ölçüleri ve aralarındaki nedensellikleri ele alır.

### Ağ Panelinde Tanı

Source: https://academia.sh/tr/kurslar/tarayici-platformu/ag-panelinde-tani

Bir isteğin zamanlama aşamalarının hangi nedene işaret ettiği, aktarılan boyut ile kaynak boyutu ayrımı, başlıkların tanıdaki rolü, şelalede bağımlılık zinciri okuma ve ölçümün yeniden üretilebilir kılınması.

Önceki ders bir ögenin neden öyle göründüğünü yanıtladı, içeriğinin neden o içerik olduğunu
yanıtlamadı. Rozetteki sayı eskiyse sorun biçemde değildir: ya istek hiç yapılmamıştır, ya
önbellekten karşılanmıştır, ya sunucu eski veriyi döndürmüştür, ya da yanıt çok geç
gelmiştir.

Ağ paneli bu dört olasılığı ayırır. Bu ders panelin ürettiği ölçüleri ve her ölçünün hangi
nedene işaret ettiğini ele alır.

#### Panelin Yanıtladığı Sorular

Panel bir istek listesi gösterir ve her satır dört soruya yanıt taşır.

**İstek gerçekten yapıldı mı?** Listede bulunmayan bir istek yapılmamıştır. Beklenen
isteğin hiç görünmemesi, kodun o yolu hiç çalıştırmadığını ya da bir koşulun tutmadığını
söyler — sunucuda aramaya gerek yoktur.

**Nereden karşılandı?** Ağdan mı, tarayıcının kendi önbelleğinden mi, yoksa servis
çalışanından mı geldiği ayrı ayrı işaretlenir. Önbellekten karşılanan bir istek listede
görünür ama ağa çıkmamıştır; eski veri sorununun en sık kaynağı budur.

**Ne kadar sürdü ve nerede bekledi?** Toplam süre tek başına bir tanı değildir; aşamalara
bölünmüş hâli tanıdır.

**Ne döndü?** Durum kodu, başlıklar ve gövde. Yanlış içerik sorunlarının çoğu burada
görülür.

#### Zamanlamanın Aşamaları

Bir isteğin toplam süresi arka arkaya gelen aşamalara ayrılır ve her aşamanın uzaması
farklı bir nedene işaret eder.

**Kuyrukta bekleme**, isteğin henüz başlamadığı süredir. Tarayıcının aynı konağa açtığı
eşzamanlı bağlantı sayısı sınırlıdır; sınır dolduğunda yeni istekler bekler. Uzun kuyruk
süresi sunucuyu değil, sayfanın aynı anda kaç istek başlattığını gösterir.

**Bağlantı kurulumu**, ad çözümlemesi, taşıma bağlantısı ve güvenli el sıkışmasından
oluşur. Yalnızca yeni bağlantılarda görünür; kalıcı bağlantı üzerinden giden istekte sıfıra
yakındır. Uzaması ağ mesafesine ve sertifika doğrulamasına işaret eder, sunucunun iş
yüküne değil.

**İlk baytı bekleme**, isteğin gönderilmesiyle ilk yanıt baytının gelmesi arasındaki
süredir. Sunucunun yanıtı üretme süresini içerir. Bu aşamanın uzaması tanıyı sunucu
tarafına taşır; istemcide yapılacak bir şey yoktur.

**Gövde indirme**, ilk bayttan son bayta kadar geçen süredir. Uzaması boyutla ya da bant
genişliğiyle ilgilidir; sıkıştırma ve boyut küçültme buraya etki eder, sunucu hızlanması
etmez.

Ayrım ölçülebilir. Aşağıdaki sunucu iki gecikmeyi ayrı ayrı üretir: ilk bayta kadar bir
işleme süresi, sonra gövdenin geri kalanı için bir aktarım süresi.

```js
// gecikmeli-sunucu.mjs — ilk bayta kadar 300 ms, govdenin kalani icin 200 ms bekler
import { createServer } from "node:http";

const bekle = (ms) => new Promise((coz) => setTimeout(coz, ms));

createServer(async (istek, yanit) => {
  yanit.sendDate = false;
  await bekle(300);                      // sunucunun isleme suresi: ilk bayti geciktirir
  yanit.writeHead(200, { "Content-Type": "application/json" });
  yanit.write('{"olcumler":[{"sicaklik":-4.2}');
  await bekle(200);                      // govdenin geri kalani: indirme suresini uzatir
  yanit.end(',{"sicaklik":-4.6}]}');
}).listen(8138, "127.0.0.1", () => console.log("dinleniyor: 127.0.0.1:8138"));
```

```bash
#!/usr/bin/env bash
# gecikmeli-sunucu.mjs'yi baslatir, tek istegin asama surelerini olcer, sonra durdurur.
node gecikmeli-sunucu.mjs > /dev/null &
sunucu=$!
sleep 1

curl -sS -o /dev/null http://127.0.0.1:8138/olcumler.json \
  -w 'ad cozumleme : %{time_namelookup} s
baglanti     : %{time_connect} s
ilk bayt     : %{time_starttransfer} s
toplam       : %{time_total} s
indirilen    : %{size_download} bayt
'

kill "$sunucu"
```

{/* mask: (?<=ad cozumleme : 0\.00)[0-9]{4}(?= s) */}
{/* mask: (?<=baglanti     : 0\.00)[0-9]{4}(?= s) */}
{/* mask: (?<=ilk bayt     : 0\.3)[0-9]{5}(?= s) */}
{/* mask: (?<=toplam       : 0\.5)[0-9]{5}(?= s) */}
```
ad cozumleme : 0.000113 s
baglanti     : 0.000441 s
ilk bayt     : 0.307935 s
toplam       : 0.508467 s
indirilen    : 50 bayt
```

Süreler ortama bağlıdır ve her çalıştırmada ondalık basamaklarda değişir; değişmeyen şey
yapıdır. Ad çözümlemesi ve bağlantı kurulumu ölçülemeyecek kadar kısadır, çünkü istek geri
döngü adresine gitmiştir; gerçek bir sunucuda bu iki değer toplam sürenin belirgin bir
bölümünü alır. İlk bayt süresi sunucunun beklettiği 300 milisaniyeyi yansıtır. Toplam süre
ile ilk bayt arasındaki 200 milisaniyelik fark gövde indirmedir. Bağlantı noktası olarak
seçilen 8138 keyfîdir ve boş olmalıdır.

Tanı kuralı buradan çıkar: **ilk bayta kadar geçen süre ile ondan sonraki süre iki ayrı
sorunun ölçüsüdür** ve tek bir toplam sayı ikisini de gizler.

#### Aktarılan Boyut ve Kaynak Boyutu

Panel bir istek için iki ayrı boyut gösterir. **Aktarılan boyut** telden geçen bayt
sayısıdır; başlıkları içerir ve sıkıştırılmışsa sıkıştırılmış hâlidir. **Kaynak boyutu**
açılmış içeriğin boyutudur.

İkisi arasındaki oran sıkıştırmanın çalışıp çalışmadığını söyler. Eşit olmaları
sıkıştırmanın uygulanmadığını gösterir; metin türündeki bir kaynak için bu bir yapılandırma
eksiğidir. Aktarılan boyutun sıfıra yakın olması isteğin önbellekten karşılandığını
gösterir.

Bu ayrım başarım tartışmasında da işe yarar. Ayrıştırma ve çalıştırma maliyeti kaynak
boyutuyla orantılıdır, aktarım maliyeti aktarılan boyutla. Sıkıştırma ikincisini düşürür,
birincisini düşürmez: iyi sıkışan büyük bir betik ağı yormaz ama ana iş parçacığını yorar.

#### Başlıkları Okumak

Yanıt başlıkları önceki konudaki tazelik kararının kanıtıdır. Bir kaynağın neden eski
geldiği sorusu, saklama izni ve ömür başlıkları okunarak yanıtlanır; doğrulayıcı varsa
koşullu isteğin yapılıp yapılmadığı ve gövdesiz yanıt dönüp dönmediği listede görünür.

Üç başlık daha tanıda sık gerekir. İçerik türü, tarayıcının yanıtı nasıl yorumlayacağını
belirler; yanlış tür, doğru gelen bir yanıtın yanlış işlenmesine yol açar. İçerik kodlaması,
sıkıştırmanın hangi biçimde uygulandığını söyler. Yanıtın hangi istek başlığına göre
değiştiğini bildiren başlık, önbelleğin farklı kullanıcılara aynı yanıtı vermesini önler;
eksikliği, bir kullanıcının yanıtının diğerine sunulmasıyla sonuçlanır.

İstek başlıkları da okunur. Kimlik bilgisinin gönderilip gönderilmediği, koşullu isteğin
doğrulayıcı taşıyıp taşımadığı ve hangi kaynağın istekte bulunduğu buradan görülür.

#### Şelalede Zincir Okumak

Şelale görünümü her isteği bir zaman çizgisi üzerinde çubuk olarak gösterir. Okunan şey
tek tek süreler değil, çubukların **birbirine göre konumudur**.

Yan yana başlayan çubuklar koşut isteklerdir. Biri bitmeden diğeri başlamıyorsa aralarında
bir bağımlılık vardır ve bu bağımlılık çoğunlukla istenmemiştir: bir betiğin indirilip
çalıştırılmasından sonra keşfedilen bir kaynak, ancak o noktada istenebilir. Kaynak Yükleme
Sırası dersinde tanımlanan ön tarama bu zinciri kısaltmak içindir; şelaledeki basamaklı
görünüm, ön taramanın kaynağı göremediğini söyler.

Aynı anda başlayan ama uzun süre bekleyen çubuklar bağlantı sınırına işaret eder. Sayfanın
ilk çizimini bekleten kaynaklar zaman çizgisindeki işaretlerle karşılaştırılır: ilk anlamlı
çizimden sonra biten bir istek, o çizimi geciktirmemiştir.

#### Araya Giren Katmanın Listede Görünüşü

Servis çalışanı kayıtlıyken liste ikiye katlanabilir. Sayfanın yaptığı istek bir satır
üretir; çalışan o isteği karşılamak için ağa çıkarsa bu ikinci bir satır olarak görünür.
İki satır aynı adrese aittir ve karıştırılırsa istek sayısı iki katı sanılır.

Ayrım tanının kendisidir. Sayfanın satırı isteğin çalışandan karşılandığını işaretliyor ve
altında ağ satırı yoksa, yanıt önbellekten gelmiştir. Her iki satır da varsa çalışan ağa
çıkmış demektir; ikisinin süreleri arasındaki fark çalışanın kendi işlem süresidir.
Sayfanın satırı görünüp çalışanın hiç devreye girmediği durumda ise ya istek kapsam
dışındadır ya da sayfa henüz denetlenmiyordur — Servis Çalışanları dersindeki denetim
kuralı burada gözlenebilir hâle gelir.

Çalışanın yaşam döngüsü de listede iz bırakır: çalışan betiğinin kendisi için yapılan
güncelleme isteği ve kurulum sırasında toplanan kaynakların istekleri, sayfanın hiç
istemediği satırlar olarak belirir.

#### Yeniden Üretilebilir Ölçüm

Ağ ölçümü, ölçüm koşulları sabitlenmeden karşılaştırılamaz. Üç koşul açıkça
belirlenmelidir.

**Önbellek durumu.** İlk ziyaret ile tekrar ziyaret bambaşka iki senaryodur ve ikisi de
gerçektir. Ölçüm hangisi için yapılıyorsa o durum kurulur; önbellek devre dışı bırakılarak
yapılan ölçüm yalnızca ilk ziyareti anlatır.

**Ağ koşulu.** Araçlar bant genişliğini ve gecikmeyi yapay olarak sınırlayabilir. Geliştirme
makinesindeki yerel ölçüm, sahadaki kullanıcının koşullarını hiçbir zaman temsil etmez;
kısıtlama, ölçümü gerçeğe yaklaştırmanın tek istemci tarafı yoludur.

**Eklentiler ve arka plan.** Tarayıcıya kurulmuş eklentiler kendi isteklerini listeye
karıştırır ve zamanlamayı etkiler. Ölçüm, eklentisiz bir kullanıcı profilinde yapılır.

Bir de kaydın kendisini korumak gerekir: gezinme sırasında listenin temizlenmesi
öntanımlıdır ve gezinmeyle sonuçlanan bir sorunun kaydı bu yüzden kaybolur. Kaydın
korunması açıkça istenir.

#### Özet

- Ağ paneli dört soruyu ayırır: istek yapıldı mı, nereden karşılandı, hangi aşamada ne kadar
  bekledi, ne döndü. Aşamalar farklı nedenlere işaret eder: kuyruk sayfanın istek sayısına,
  bağlantı kurulumu mesafeye, ilk bayt sunucuya, indirme boyuta.
- Servis çalışanı kayıtlıyken aynı adres iki satır üretebilir; ikinci satırın yokluğu
  yanıtın önbellekten geldiğini gösterir.
- Aktarılan boyut ile kaynak boyutu arasındaki fark sıkıştırmayı ölçer; ayrıştırma maliyeti
  kaynak boyutuyla orantılıdır.
- Başlıklar tazelik kararının, içerik yorumlamasının ve önbellek ayrımının kanıtıdır.
- Şelalede okunan şey süreler değil, çubukların birbirine göre konumudur; basamaklı görünüm
  keşif zinciri anlamına gelir.
- Ölçüm önbellek durumu, ağ koşulu ve tarayıcı profili sabitlenmeden yeniden üretilemez.

#### Sonraki Adım

Ağ paneli isteğin ne zaman bittiğini söyler, ama yanıt geldikten sonra sayfanın neden hâlâ
donuk kaldığını söylemez. Ölçüm listesi indirildiği hâlde ekranın geç güncellenmesinin
nedeni ağda değil, tek bir iş parçacığında sıraya girmiş işlerdedir: ayrıştırma, biçem
hesabı, yerleşim, boyama ve araya giren her betik aynı sırayı paylaşır. Bu sıranın nerede
tıkandığı, isteklerin listesiyle değil zaman içindeki iş dökümüyle görülür. Sonraki ders
bu dökümü üreten kaydı ve okunma biçimini ele alır.

### Başarım Kaydı

Source: https://academia.sh/tr/kurslar/tarayici-platformu/basarim-kaydi

Ana iş parçacığındaki işlerin zaman içindeki dökümü; örneklemenin ürettiği kendi süresi ve toplam süre ayrımı, uzun görev ile kare bütçesi hesabı, yerleşim sarsıntısının sayılması ve kaydın yorumlanmasındaki tuzaklar.

Ağ paneli isteğin ne zaman bittiğini söyler, yanıt geldikten sonra sayfanın neden hâlâ
donuk kaldığını söylemez. Ölçüm listesi indirildiği hâlde ekranın geç güncellenmesinin
nedeni ağda değil, tek bir iş parçacığında sıraya girmiş işlerdedir.

Bu ders, o sıranın zaman içindeki dökümünü üreten **başarım kaydını** ve okunma biçimini
ele alır.

#### Kayıt Ne Toplar

Kayıt tek bir liste değil, aynı zaman ekseni üzerine dizilmiş birkaç izdir.

**Ana iş parçacığı izi** en yoğun olanıdır: her görevin ne zaman başladığını, ne kadar
sürdüğünü ve içinde hangi çağrıların bulunduğunu taşır. **Çizim izi** hangi karelerin
zamanında üretildiğini gösterir. **Ağ izi** isteklerin zaman eksenindeki yerini verir ve
önceki dersteki listenin zamanla hizalanmış hâlidir. **İm ve ölçüm izi** ise kaydın çalıştığı
koddan gelen etiketleri taşır.

İzlerin aynı eksende olması kaydın asıl değeridir: bir isteğin bitişiyle ekrandaki
değişimin arasındaki boşluk, ancak iki iz yan yana konduğunda görülür.

#### Tek Sıra: Ana İş Parçacığı

Tarayıcının sayfa başına yürüttüğü işlerin çoğu tek bir sırayı paylaşır: HTML
ayrıştırması, biçem hesabı, yerleşim, boyama hazırlığı ve bütün betikler. Eşzamansız
JavaScript ve Çalışma Zamanı kursundaki Olay Döngüsü dersinde kurulan model burada görsel
karşılığını bulur; kaydın gösterdiği şey o kuyruğun gerçekleşmiş hâlidir.

Sıranın tek olması iki sonuç doğurur. Bir görev sürerken kullanıcı girdisi işlenemez;
tıklama kaydedilir ama yanıt gecikir. Ve bir görev sürerken yeni bir kare üretilemez;
animasyon durur.

#### Örnekleme ve Alev Grafiği

Ana iş parçacığı izi çoğunlukla **örneklemeyle** toplanır: kaydedici sabit aralıklarla
çağrı yığıtının o anki hâlini kaydeder. Alev grafiği bu örneklerden türetilir. Yatay
genişlik süredir; **dikey derinlik zaman değil, çağrı iç içeliğidir.**

Grafikten okunan iki sayı vardır ve karıştırılmaları yanlış eniyilemeye yol açar.

```js
// ornekleme.mjs — ornek yiginlarindan kendi suresi ve toplam sure hesabi
const ARALIK = 1; // ms: ornekleyici her 1 ms'de bir cagri yiginini kaydeder

// Her satir, o anki cagri yigini (en distan en ice).
const ornekler = [
  ["kok", "olcumleriCiz"],
  ["kok", "olcumleriCiz", "satirKur"],
  ["kok", "olcumleriCiz", "satirKur"],
  ["kok", "olcumleriCiz", "satirKur", "tarihBicimle"],
  ["kok", "olcumleriCiz", "satirKur", "tarihBicimle"],
  ["kok", "olcumleriCiz", "satirKur", "tarihBicimle"],
  ["kok", "olcumleriCiz", "satirKur"],
  ["kok", "olcumleriCiz", "esikHesapla"],
  ["kok", "olcumleriCiz", "esikHesapla"],
  ["kok", "kutugeYaz"],
];

const toplam = new Map();
const kendi = new Map();
const artir = (harita, ad) => harita.set(ad, (harita.get(ad) ?? 0) + ARALIK);

for (const yigin of ornekler) {
  for (const ad of new Set(yigin)) artir(toplam, ad); // her cerceve icin toplam sure
  artir(kendi, yigin[yigin.length - 1]);              // yalnizca en icteki icin kendi suresi
}

console.log("islev".padEnd(16) + "toplam".padStart(8) + "kendi".padStart(8));
for (const [ad, t] of [...toplam].sort((a, b) => b[1] - a[1]))
  console.log(ad.padEnd(16) + `${t} ms`.padStart(8) + `${kendi.get(ad) ?? 0} ms`.padStart(8));

// Uzun gorev esigi ve kare butcesi
const UZUN_GOREV = 50;      // ms
const KARE_BUTCESI = 1000 / 60;

const gorevler = [
  { ad: "gezinme isleme", sure: 132 },
  { ad: "olcum listesi cizimi", sure: 68 },
  { ad: "girdi isleyicisi", sure: 12 },
  { ad: "kaydirma isleyicisi", sure: 23 },
];

console.log("\ngorev".padEnd(24) + "sure".padStart(8) + "  uzun gorev?  kacan kare");
for (const g of gorevler) {
  const kacan = Math.max(0, Math.floor(g.sure / KARE_BUTCESI));
  console.log(
    g.ad.padEnd(24) + `${g.sure} ms`.padStart(8) +
    (g.sure > UZUN_GOREV ? "  evet" : "  hayir").padEnd(15) + kacan);
}
console.log(`\nkare butcesi: ${KARE_BUTCESI.toFixed(2)} ms (saniyede 60 kare)`);
```

```
islev             toplam   kendi
kok                10 ms    0 ms
olcumleriCiz        9 ms    1 ms
satirKur            6 ms    3 ms
tarihBicimle        3 ms    3 ms
esikHesapla         2 ms    2 ms
kutugeYaz           1 ms    1 ms

gorev                      sure  uzun gorev?  kacan kare
gezinme isleme            132 ms  evet         7
olcum listesi cizimi       68 ms  evet         4
girdi isleyicisi           12 ms  hayir        0
kaydirma isleyicisi        23 ms  hayir        1

kare butcesi: 16.67 ms (saniyede 60 kare)
```

**Toplam süre**, bir işlevin ve onun çağırdığı her şeyin süresidir. **Kendi süresi**,
yalnızca o işlevin gövdesinde geçen süredir. Kök çerçevenin toplam süresi kaydın tamamıdır
ve kendi süresi sıfırdır: hiçbir iş yapmaz, yalnızca çağırır.

Eniyileme kararı kendi süresine bakılarak verilir. Toplam süresi büyük bir işlevi
hızlandırmaya çalışmak, zaman aslında onun çağırdığı bir alt işlevde geçiyorsa boşa emektir.
Çıktıda `olcumleriCiz` toplamda dokuz milisaniye tutar ama kendi gövdesinde yalnızca bir
milisaniye harcar; asıl yük `satirKur` ve onun çağırdığı biçimleme işlevindedir.

#### Uzun Görev ve Kare Bütçesi

İkinci tablo iki eşiği yan yana koyar.

**Uzun görev** eşiği, ana iş parçacığını elli milisaniyeden uzun süre meşgul eden görevleri
ayırır. Eşiğin gerekçesi girdi yanıtıdır: kullanıcı bir düğmeye bastığında yanıtın bu
mertebede gecikmesi fark edilir hâle gelir.

**Kare bütçesi**, ekranın yenileme hızından türetilir. Örnekteki hesap saniyede altmış kare
varsayımıyla yapılmıştır ve bütçeyi yaklaşık on yedi milisaniyeye indirir; farklı yenileme
hızına sahip bir ekranda bütçe değişir, hesabın biçimi değişmez. Bütçeye sığmayan her
görev, süresi boyunca kare üretilmesini engeller — son sütun bunu sayar.

İki eşik farklı sorunları ölçer. Elli milisaniyelik bir görev uzun görev sayılmaz ama üç
kareyi düşürür. Kaydırma ve animasyon akıcılığı kare bütçesiyle, tıklama yanıtı uzun görev
eşiğiyle değerlendirilir.

#### Düzen Sarsıntısı

Kayıtta en sık karşılaşılan örüntü, betiğin içine serpilmiş yerleşim hesaplarıdır. Nedeni
tanımlıdır: bir ölçü sorulduğunda tarayıcı, bekleyen değişiklikler varsa yerleşimi **hemen**
hesaplamak zorundadır. Ölçü okuma ile biçem yazma sırayla tekrarlandığında bu hesap her
turda yeniden zorlanır.

```js
// duzen-sarsintisi.mjs — okuma ile yazmanin siralanmasi kac yerlesim hesabi uretir
function motor() {
  let kirli = false;
  let zorlananHesap = 0;
  return {
    get zorlananHesap() { return zorlananHesap; },
    yaz() { kirli = true; },                       // yerlesimi gecersiz kilar
    oku() { if (kirli) { zorlananHesap += 1; kirli = false; } }, // olcu istemek hesabi zorlar
    kareSonu() { if (kirli) { kirli = false; return 1; } return 0; }, // normal hesap
  };
}

function calistir(sira) {
  const m = motor();
  for (const islem of sira) m[islem]();
  const kareSonuHesabi = m.kareSonu();
  return { zorlanan: m.zorlananHesap, kareSonu: kareSonuHesabi };
}

const SATIR = 5;
const dongude = [];
for (let i = 0; i < SATIR; i++) dongude.push("oku", "yaz"); // her satir icin oku sonra yaz

const ayrilmis = [
  ...Array.from({ length: SATIR }, () => "oku"),
  ...Array.from({ length: SATIR }, () => "yaz"),
];

for (const [ad, sira] of [["dongu icinde oku-yaz", dongude], ["once tum okumalar", ayrilmis]]) {
  const s = calistir(sira);
  console.log(
    `${ad.padEnd(22)} zorlanan yerlesim hesabi: ${s.zorlanan}` +
    `   kare sonundaki hesap: ${s.kareSonu}`);
}
```

```
dongu icinde oku-yaz   zorlanan yerlesim hesabi: 4   kare sonundaki hesap: 1
once tum okumalar      zorlanan yerlesim hesabi: 0   kare sonundaki hesap: 1
```

İki sıralama aynı işi yapar ve aynı sonucu üretir; farkları tetikledikleri hesap sayısıdır.
Döngü içinde okuyup yazan sıra, ilk okuma dışında her okumada yerleşimi zorlar. Okumaları
öne alan sıra hiç zorlamaz; tek hesap, karenin sonunda tarayıcının kendi zamanlamasıyla
yapılır.

Kayıtta bu örüntü, betik görevinin içine yerleşmiş yerleşim işleri olarak görünür ve
araçlar bunu ayrıca uyarı olarak işaretler. Düzeltmenin biçimi her zaman aynıdır:
**okumaları topla, sonra yazmaları topla.** Beş satırlık bir listede fark önemsizdir; yüz
satırlık bir ölçüm tablosunda kare bütçesinin tamamını yer.

#### Kendi Kodunu İşaretlemek

Örnekleme, kısa süren ve seyrek çalışan kodu güvenilir biçimde yakalamaz. Bir işlevin
kayıtta hiç görünmemesi, hiç çalışmadığı anlamına gelmez; iki örnek arasına sığmış olabilir.

Bunun karşılığı, kodun kendi sınırlarını bildirmesidir. Bir noktaya **im** konur, iki im
arasındaki aralık bir **ölçüm** olarak adlandırılır ve bu adlandırılmış aralıklar kayıtta
kendi izlerinde görünür. Eşzamansız JavaScript ve Çalışma Zamanı kursundaki Başarım
Profilleme dersinde tanımlanan bu ikili, kayıttaki isimsiz görevleri anlamlı adlara bağlar.

İmlerin bir yararı da eşzamansız sınırları taşımasıdır: veri isteğinin başladığı an ile
listenin çizildiği an arasındaki aralık, tek bir görev içinde geçmediği için alev
grafiğinden okunamaz; adlandırılmış bir aralık olarak konursa okunur.

#### Kaydın Yorumlanmasındaki Tuzaklar

Üç tuzak sık görülür.

**Örnekleme kesin değildir.** Küçük sayılar örnek aralığından etkilenir; iki milisaniyelik
bir fark gürültü olabilir. Karar verilecek büyüklükler örnek aralığının belirgin katları
olmalıdır.

**Ölçmek etkiler.** Kayıt açıkken sayfa daha yavaş çalışır. Mutlak süreler değil, aynı
koşulda alınmış iki kayıt arasındaki oran anlamlıdır.

**Geliştirme çıktısı üretim çıktısı değildir.** Küçültülmemiş, ek denetimler taşıyan bir
çıktının kaydı, kullanıcının karşılaştığı davranışı temsil etmez. Ölçüm üretim
yapılandırmasıyla yapılır; işlev adlarının okunabilirliği ayrı bir konudur ve kaynak
haritalarıyla çözülür.

#### Özet

- Başarım kaydı birkaç izi aynı zaman ekseninde toplar; asıl değeri izler arasındaki
  boşlukların görünür olmasıdır.
- Ana iş parçacığı tek bir sıradır: ayrıştırma, biçem, yerleşim, boyama ve bütün betikler
  onu paylaşır.
- Alev grafiğinde genişlik süre, derinlik çağrı iç içeliğidir; eniyileme kararı toplam
  süreye değil kendi süresine bakılarak verilir.
- Uzun görev eşiği girdi yanıtını, kare bütçesi akıcılığı ölçer; ikisi farklı sorunlara
  işaret eder.
- Ölçü okuma ile biçem yazmanın sıralanması yerleşim hesabını zorlar; okumaları öne almak
  zorlanan hesabı sıfıra indirir.
- Örnekleme kısa kodu kaçırır, kayıt ölçtüğünü yavaşlatır ve geliştirme çıktısı üretim
  davranışını temsil etmez.

#### Sonraki Adım

Başarım kaydı bir anın dökümünü verir: kaydın açık olduğu birkaç saniyede ne olduğunu.
Bazı sorunlar bu pencereye sığmaz. Ölçüm sayfası açık kaldıkça yavaşlıyor, listeyi her
yenileyişte biraz daha ağırlaşıyorsa, tek bir kayıtta görülecek bir uzun görev yoktur;
biriken şey zamanla artan ve serbest bırakılmayan bellektir. Bunun ölçüsü süre değil,
programın hâlâ erişebildiği nesnelerin toplamıdır. Sonraki ders bu toplamı ölçen aracı ve
sızıntının nasıl bulunacağını ele alır.

### Bellek İncelemesi

Source: https://academia.sh/tr/kurslar/tarayici-platformu/bellek-incelemesi

Sızıntının erişilebilirlik üzerinden tanımı; sığ boyut, tutulan boyut ve tutucu yolu hesabı, anlık görüntülerin karşılaştırılması, güçlü ile zayıf başvurunun ölçülen farkı ve tarayıcıya özgü sızıntı kaynakları.

Başarım kaydı bir anın dökümünü verir. Bazı sorunlar bu pencereye sığmaz: ölçüm sayfası
açık kaldıkça yavaşlıyor, listeyi her yenileyişte biraz daha ağırlaşıyorsa, tek bir kayıtta
görülecek bir uzun görev yoktur. Biriken şey zamanla artan ve serbest bırakılmayan
bellektir.

Bu ders o birikimi ölçen aracı ve birikimin kaynağını bulma yolunu ele alır.

#### Sızıntının Tanımı: Erişilebilirlik

Eşzamansız JavaScript ve Çalışma Zamanı kursundaki Bellek Yaşam Döngüsü dersinde kurulan
model burada tanının dayanağıdır: çöp toplayıcı, **kök kümesinden erişilebilir olmayan**
her nesneyi geri alır. Erişilebilir olan hiçbir nesne toplanmaz.

Buradan sızıntının tanımı çıkar. **Bellek sızıntısı**, artık kullanılmayan bir nesnenin
hâlâ erişilebilir kalmasıdır. Çöp toplayıcının hatası değildir; nesnenin kullanılmadığını
bilemez, yalnızca erişilebilirliği bilir. Tanı bu yüzden "hangi nesne büyük" sorusuyla
değil, **"bu nesneye kim hâlâ başvuruyor"** sorusuyla yapılır.

#### Sığ Boyut, Tutulan Boyut ve Tutucu Yolu

Bellek görünümlerinde bir nesne için iki ayrı boyut listelenir ve aralarındaki fark tanının
merkezindedir.

```js
// tutulan-boyut.mjs — sig boyut, tutulan boyut ve tutucu yolu hesabi
const SIG = {
  kok: 16, dinleyici: 32, kapanis: 32, olcumDizisi: 8192,
  kopmusSatir: 128, hucre1: 64, hucre2: 64,
  pano: 64, rozet: 48, ortakBicem: 256,
};

const KENAR = {
  kok: ["dinleyici", "pano"],
  dinleyici: ["kapanis"],
  kapanis: ["kopmusSatir", "olcumDizisi"],
  kopmusSatir: ["hucre1", "hucre2"],
  olcumDizisi: [], hucre1: [], hucre2: [],
  pano: ["rozet", "ortakBicem"],
  rozet: ["ortakBicem"],
  ortakBicem: [],
};

const KOK_KUMESI = ["kok"];

function erisilebilir(haric = null) {
  const gorulen = new Set();
  const yigit = KOK_KUMESI.filter((d) => d !== haric);
  while (yigit.length) {
    const d = yigit.pop();
    if (gorulen.has(d)) continue;
    gorulen.add(d);
    for (const komsu of KENAR[d]) if (komsu !== haric) yigit.push(komsu);
  }
  return gorulen;
}

const boyut = (kume) => [...kume].reduce((t, d) => t + SIG[d], 0);

function tutulanBoyut(dugum) {
  const oncesi = erisilebilir();
  const sonrasi = erisilebilir(dugum);
  return boyut(oncesi) - boyut(sonrasi);
}

// Tutucu yolu: kokten dugume giden en kisa yol.
function tutucuYolu(hedef) {
  const kuyruk = KOK_KUMESI.map((d) => [d]);
  const gorulen = new Set(KOK_KUMESI);
  while (kuyruk.length) {
    const yol = kuyruk.shift();
    const son = yol[yol.length - 1];
    if (son === hedef) return yol;
    for (const komsu of KENAR[son])
      if (!gorulen.has(komsu)) { gorulen.add(komsu); kuyruk.push([...yol, komsu]); }
  }
  return null;
}

console.log("dugum".padEnd(14) + "sig".padStart(7) + "tutulan".padStart(9));
for (const d of Object.keys(SIG))
  console.log(d.padEnd(14) + String(SIG[d]).padStart(7) + String(tutulanBoyut(d)).padStart(9));

console.log("\ntutucu yolu (kopmusSatir):", tutucuYolu("kopmusSatir").join(" → "));
console.log("tutucu yolu (ortakBicem) :", tutucuYolu("ortakBicem").join(" → "));
```

```
dugum             sig  tutulan
kok                16     8896
dinleyici          32     8512
kapanis            32     8480
olcumDizisi      8192     8192
kopmusSatir       128      256
hucre1             64       64
hucre2             64       64
pano               64      368
rozet              48       48
ortakBicem        256      256

tutucu yolu (kopmusSatir): kok → dinleyici → kapanis → kopmusSatir
tutucu yolu (ortakBicem) : kok → pano → ortakBicem
```

**Sığ boyut**, nesnenin kendi alanıdır. **Tutulan boyut**, o nesne serbest bırakıldığında
geri kazanılacak toplam alandır — yani nesnenin ve yalnızca onun üzerinden erişilen her
şeyin toplamı.

Dinleyici satırı ayrımı özetler: sığ boyutu otuz iki bayttır, tutulan boyutu sekiz binin
üzerinde. Bir listede sığ boyuta göre sıralanan görünümde bu dinleyici hiç dikkat çekmez;
tutulan boyuta göre sıralandığında listenin başına çıkar. **Sızıntı avında sıralama ölçütü
tutulan boyuttur.**

`rozet` ile `ortakBicem` karşılaştırması ikinci kuralı verir. Rozetin tutulan boyutu
yalnızca kendi sığ boyutudur, çünkü işaret ettiği biçem nesnesine panodan da
başvurulmaktadır; rozet serbest bırakılsa o nesne yine erişilebilir kalır. **Paylaşılan
nesneler hiçbir tutucunun tutulan boyutuna girmez**; bu yüzden tutulan boyutların toplamı
öbeğin boyutunu vermez.

**Tutucu yolu**, kök kümesinden nesneye uzanan başvuru zinciridir ve sızıntının cevabıdır.
Kopmuş satırın yolu, onu tutan şeyin bir kapanış, kapanışı tutan şeyin bir dinleyici
olduğunu söyler; düzeltilecek yer bellekte değil, o dinleyicinin kaldırılmadığı koddadır.

#### Ölçmek: Anlık Görüntü ve Karşılaştırma

Tek bir **öbek anlık görüntüsü** bir andaki durumu verir; sızıntı ise bir eğilimdir. Bu
yüzden ölçüm karşılaştırmayla yapılır.

Yerleşik yöntem üç görüntüye dayanır. Şüphelenilen işlem — ölçüm listesinin açılıp
kapanması — bir kez yapılır ve birinci görüntü alınır; bu, ilk kullanımda oluşan ve normal
sayılan yapıları temeline koyar. Aynı işlem birkaç kez daha tekrarlanır ve ikinci görüntü
alınır. İşlem bir kez daha tekrarlanıp üçüncü görüntü alınır. **İkinci ile üçüncü görüntü
arasında var olmayı sürdüren nesneler** sızıntı adaylarıdır; birinci görüntüye göre artan
ama ikincide kaybolanlar geçici tahsislerdir.

Karşılaştırmanın anlamlı olması için görüntü almadan önce çöp toplamanın çalışmış olması
gerekir; araçlar bunu görüntü alırken kendiliğinden yapar. Aksi hâlde ölçülen artış,
henüz toplanmamış çöpten ibaret olabilir.

#### Güçlü ve Zayıf Başvuru

Sızıntı kaynaklarının en yaygını, nesneleri anahtar olarak saklayan yardımcı yapılardır:
bir ögeye iliştirilmiş veriyi tutan bir eşleme, o öge ağaçtan çıksa bile ona başvurmayı
sürdürür. Farkın ölçüsü doğrudan alınabilir.

Aşağıdaki ölçüm çöp toplayıcıyı elle çağırır ve bunun için `node --expose-gc` ile
çalıştırılmalıdır; bayrak verilmeden çalıştırıldığında çağrı tanımsızdır.

```js
// sizinti-olcum.mjs — calistirma: node --expose-gc sizinti-olcum.mjs
// Iki kayit defteri ayni isi yapar; farklari, anahtar olarak tuttuklari nesneye
// guclu mu zayif mi basvurmalaridir.
const MB = (bayt) => (bayt / 1024 / 1024).toFixed(1);

function olc() {
  globalThis.gc();                     // --expose-gc olmadan tanimsizdir
  return process.memoryUsage().heapUsed;
}

function dene(defter, sayi) {
  const taban = olc();
  for (let i = 0; i < sayi; i++) {
    const dugum = { kimlik: i, olcum: new Array(64).fill(i) };
    defter.set(dugum, { sonGuncelleme: i });
    // dongu turu bitince dugum'e disaridan hicbir basvuru kalmaz
  }
  return olc() - taban;
}

const SAYI = 200_000;
console.log(`${SAYI} dugum olusturulup birakildiktan sonra obekte kalan:`);
console.log("  guclu basvurulu defter :", MB(dene(new Map(), SAYI)), "MB");
console.log("  zayif basvurulu defter :", MB(dene(new WeakMap(), SAYI)), "MB");
```

```
200000 dugum olusturulup birakildiktan sonra obekte kalan:
  guclu basvurulu defter : 127.5 MB
  zayif basvurulu defter : 1.0 MB
```

Sayılar çalışma zamanına ve makineye bağlıdır; oran ise kararlıdır. Güçlü başvurulu defter
düğümlerin hepsini erişilebilir tutar ve hiçbiri toplanmaz. Zayıf başvurulu defterde
anahtar, nesneyi hayatta tutmaz; başka başvurusu kalmayan düğüm toplanır ve onunla birlikte
defterdeki girdi de gider.

Kural şöyle özetlenir: **bir nesneye iliştirilmiş yan veri, o nesnenin ömrünü uzatmamalıdır.**
Zayıf başvuru bunu sağlayan yapıdır ve bedeli, defterin içeriğinin dolaşılamamasıdır —
hangi anahtarların hâlâ var olduğu sorulamaz, çünkü soru sorulabilseydi anahtarlar hayatta
tutulmuş olurdu.

#### Tarayıcıya Özgü Sızıntı Kaynakları

Sayfa bağlamında dört örüntü tekrarlanır ve dördü de tutucu yolunda tanınabilir izler
bırakır.

**Kopmuş düğümler.** Ağaçtan çıkarılmış ama bir değişkenden, diziden ya da kapanıştan
başvurulmayı sürdüren DOM düğümleri. Bellek görünümlerinde ayrı bir sınıf olarak
işaretlenirler; bir kopmuş düğümün alt ağacının tamamı onunla birlikte tutulur. DOM API'si
dersindeki durağan listelerin uzun süre saklanması bu sonucu üretir.

**Kaldırılmayan dinleyiciler.** Bağlanan her dinleyici hem işlevi hem de o işlevin
kapanışını hedef düğüme bağlar. Olay Modeli dersinde tanıtılan iptal işareti seçeneği bu
sorunu kaynağında çözer: tek bir iptalle o işarete bağlı bütün dinleyiciler kalkar. Özel
öge yazan taraf için karşılığı, bağlanma geri çağrısında kurulanın bağlantı kesilme geri
çağrısında sökülmesidir.

**Duran zamanlayıcılar ve gözlemciler.** Yinelenen bir zamanlayıcı, geri çağrısının
kapanışını süresiz erişilebilir tutar. Aynısı, gözlemlediği öge ağaçtan çıksa bile durdurulmayan
gözlemciler için geçerlidir. İkisinin de karşılığı açık bir durdurma çağrısıdır.

**Sınırsız büyüyen önbellekler.** Program içinde tutulan bir sonuç eşlemesi, sınır ve
tahliye politikası verilmediğinde ömür boyu büyür. İşlemci Önbelleği dersinde tanımlanan
tahliye politikası kavramı burada uygulanır: sınırsız önbellek, önbellek değil sızıntıdır.

#### Özet

- Bellek sızıntısı, kullanılmayan bir nesnenin erişilebilir kalmasıdır; tanı "kim
  başvuruyor" sorusuyla yapılır.
- Sığ boyut nesnenin kendi alanı, tutulan boyut serbest bırakıldığında geri kazanılacak
  toplam alandır; sızıntı avında sıralama ölçütü tutulan boyuttur.
- Paylaşılan nesneler hiçbir tutucunun tutulan boyutuna girmez; tutulan boyutların toplamı
  öbek boyutunu vermez.
- Tutucu yolu, kök kümesinden nesneye uzanan zincirdir ve düzeltilecek kod satırını
  gösterir.
- Sızıntı bir eğilimdir; ardışık anlık görüntülerde var olmayı sürdüren nesneler adaydır.
- Nesneye iliştirilen yan veri onun ömrünü uzatmamalıdır; zayıf başvurulu yapılar bunu
  sağlar, karşılığında içerikleri dolaşılamaz.

#### Sonraki Adım

Buraya kadarki araçlar sonucu ölçtü: hangi kural kazandı, istek ne kadar sürdü, hangi işlev
ana iş parçacığını tuttu, hangi nesne bellekte kaldı. Hiçbiri "bu satır çalıştığında
değişkenin değeri neydi" sorusunu yanıtlamaz. Ölçüm listesinin bir satırının neden eksik
çizildiği, kaydın hangi koşulda yanlış dala girdiği, ancak programı seçilen bir noktada
durdurup o andaki durumu okuyarak görülür. Sonraki ders bu duraklatma mekanizmasını ve
üretim çıktısında durdurulan kodun kaynaktaki karşılığının nasıl bulunduğunu ele alır.

### Kaynak Hata Ayıklama

Source: https://academia.sh/tr/kurslar/tarayici-platformu/kaynak-hata-ayiklama

Programı seçilen bir noktada durdurup durumu okumak; kesme noktası türlerinin yanıtladığı sorular, adımlama ve kapsam okuma, üretim çıktısındaki konumun kaynak haritasıyla çözülmesi ve haritanın dağıtımı.

Buraya kadarki araçlar sonucu ölçtü: hangi kural kazandı, istek ne kadar sürdü, hangi işlev
ana iş parçacığını tuttu, hangi nesne bellekte kaldı. Hiçbiri "bu satır çalıştığında
değişkenin değeri neydi" sorusunu yanıtlamaz.

Ölçüm listesinin bir satırının neden eksik çizildiği, ancak programı seçilen bir noktada
durdurup o andaki durumu okuyarak görülür. Bu ders duraklatma mekanizmasını ve üretim
çıktısında durdurulan kodun kaynaktaki karşılığının nasıl bulunduğunu ele alır.

#### Duraklatmanın Anlamı

Bir kesme noktasına ulaşıldığında ana iş parçacığı durur. Duran şey yalnızca o işlev
değildir: sayfanın tamamıdır. Zamanlayıcılar tetiklenmez, olaylar işlenmez, kare
üretilmez. Okunan durum, tam olarak o komutun çalışmak üzere olduğu andaki durumdur.

İki sonuç doğar. Birincisi, **zamana bağlı davranış duraklatmayla bozulur**: bir animasyonun
ortasında durup devam edildiğinde geçen süre gerçekte geçmiştir ve süreye dayalı hesaplar
sıçrar. Zamanlamayla ilgili sorunlar duraklatarak değil, kayıtla incelenir.

İkincisi, **durdurulan noktada okunan değer, yazdırılan değerden güvenilirdir.** Kütüğe
yazılan bir nesne, konsolda açıldığı anda okunur; bu, yazma anından sonradır ve nesne o
arada değişmiş olabilir. Kesme noktası bu belirsizliği ortadan kaldırır.

#### Kesme Noktası Türleri ve Yanıtladıkları Soru

Kesme noktasının türü, sorulan soruya göre seçilir.

**Satır kesme noktası** "bu satıra gelindi mi ve o an durum neydi" sorusunu yanıtlar. En
temel türdür ve satırın hangi kodda olduğu bilindiğinde kullanılır.

**Koşullu kesme noktası** aynı satırı yalnızca bir koşul sağlandığında durdurur. Yüz
satırlık bir listede yalnızca eksik çizilen satırda durmak için gereklidir; koşulsuz bir
kesme noktası burada yüz kez durur ve kullanılamaz hâle gelir.

**Kayıt noktası** durdurmaz, yalnızca bir ifadenin değerini kütüğe yazar. Kodu
değiştirmeden kütük satırı eklemenin yoludur ve bu yüzden sürüm kontrolüne sızacak geçici
yazdırma satırlarını ortadan kaldırır.

**İstisna kesme noktası** "bu hata nereden atıldı" sorusunu yanıtlar ve iki kipi vardır:
her istisnada durmak, ya da yalnızca yakalanmayanlarda. Birinci kip, bir hatanın
yakalanıp yutulduğu yerleri bulur.

**Ağaç değişimi kesme noktası** "bu düğümü kim değiştirdi" sorusunu yanıtlar. Öznitelik
değişiminde, çocuk ekleme–çıkarmada ya da düğümün kendisinin kaldırılmasında durur.
Beklenmedik biçimde kaybolan ya da değişen bir ögenin sorumlusunu bulmanın en kısa yoludur;
kodun hangi bölümünde arayacağını bilmek gerekmez.

**Olay kesme noktası** belirli bir olay türü işlenmeye başladığında durur ve "bu tıklamayı
hangi dinleyici karşılıyor" sorusunu yanıtlar. Olay Devri dersinde kurulan yapıda
dinleyicinin hedeften uzak bir atada bulunması bu aracı gerekli kılar.

**İstek kesme noktası** adresi belirli bir örüntüye uyan bir ağ isteği yapıldığında durur
ve isteği başlatan çağrı zincirini gösterir.

#### Adımlama, Kapsam ve Çağrı Yığıtı

Durdurulmuş programda üç görünüm birlikte okunur.

**Çağrı yığıtı** o noktaya nasıl gelindiğini söyler. Her çerçeve seçilebilir ve seçilen
çerçevenin kapsamı okunur; hatalı değerin nereden geldiği yığıtta yukarı çıkılarak
bulunur. Eşzamansız sınırlar da yığıta katılır: bir sözün ardından çalışan kodun, sözü
başlatan çağrıyla ilişkisi ayrı bir bölüm olarak gösterilir. Bu olmadan yığıt olay
döngüsünde başlar ve hiçbir şey anlatmaz.

**Kapsam görünümü** seçilen çerçevedeki yerel değişkenleri, kapanışta yakalanmış değerleri
ve genel kapsamı ayrı ayrı listeler. Kapanış bölümü tanıda özellikle değerlidir: bir
değişkenin beklenenden farklı olması, çoğunlukla döngü değişkeninin yakalanma biçiminden
gelir.

**Adımlama** üç kipte yapılır. Üzerinden atlama, bulunulan satırdaki çağrıyı çalıştırır ve
sonraki satırda durur. İçine girme, çağrılan işlevin ilk satırında durur. Dışına çıkma,
bulunulan işlev dönene kadar çalıştırır ve çağırana geri döner.

Adımlamanın pratik sorunu, yardımcı kodun içine düşmektir: bir olay dinleyicisinin içine
girmeye çalışırken araya giren altyapı çerçeveleri adımlamayı dağıtır. Araçlar belirli
dosyaların adımlamada **yok sayılmasına** izin verir; yok sayılan dosyaların çerçeveleri
yığıtta gizlenir ve içine girme onları atlar. Kendi kodunda kalmak bu ayarla sağlanır.

#### Üretim Çıktısında Durmak: Kaynak Haritaları

Dağıtılan betik, yazılan betik değildir: adları kısaltılmış, satırları birleştirilmiş,
belki başka bir söz dizimine indirgenmiştir. Bu çıktıda durdurulan bir program, okunamayan
tek bir satır gösterir.

Modüller, Araçlar ve Ekosistem kursundaki Kaynak Haritaları dersinde tanımlanan yapı bu
boşluğu kapatır: üretilen dosyadaki her konumu özgün dosyadaki bir konuma bağlayan bir
eşleme tablosu. Tablo, yerden kazanmak için farklarla ve değişken uzunluklu niceliklerle
kodlanır. Çözümü hesaplanabilir bir işlemdir.

```js
// kaynak-haritasi.mjs — VLQ kodlama/cozme ve uretilen konumun kaynaktaki karsiligi
const ABECE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

function vlqKodla(sayi) {
  let deger = sayi < 0 ? (-sayi << 1) | 1 : sayi << 1; // isaret en dusuk bitte
  let cikti = "";
  do {
    let parca = deger & 0b11111;
    deger >>>= 5;
    if (deger > 0) parca |= 0b100000;                  // devam biti
    cikti += ABECE[parca];
  } while (deger > 0);
  return cikti;
}

function vlqCoz(dizgi) {
  const sayilar = [];
  let deger = 0, kaydirma = 0;
  for (const karakter of dizgi) {
    const parca = ABECE.indexOf(karakter);
    deger += (parca & 0b11111) << kaydirma;
    if (parca & 0b100000) { kaydirma += 5; continue; } // devam biti: sonraki karaktere gec
    sayilar.push(deger & 1 ? -(deger >>> 1) : deger >>> 1);
    deger = 0; kaydirma = 0;
  }
  return sayilar;
}

// Saltik eslemeler: uretilen dosyadaki konum → kaynaktaki konum
const ESLEMELER = [
  [ // uretilen satir 0
    { uretilenSutun: 0, kaynak: 0, satir: 0, sutun: 0, ad: 0 },
    { uretilenSutun: 12, kaynak: 0, satir: 4, sutun: 2, ad: 1 },
    { uretilenSutun: 31, kaynak: 0, satir: 5, sutun: 14, ad: null },
  ],
  [ // uretilen satir 1
    { uretilenSutun: 0, kaynak: 0, satir: 9, sutun: 0, ad: null },
    { uretilenSutun: 8, kaynak: 0, satir: 11, sutun: 4, ad: null },
  ],
];

// Saltik eslemeleri fark dizisine cevirip kodlar: alanlar farklarla saklanir.
function mappingsUret(eslemeler) {
  let oncekiKaynak = 0, oncekiSatir = 0, oncekiSutun = 0, oncekiAd = 0;
  return eslemeler.map((satirEslemeleri) => {
    let oncekiUretilenSutun = 0;
    return satirEslemeleri.map((e) => {
      const alanlar = [e.uretilenSutun - oncekiUretilenSutun, e.kaynak - oncekiKaynak,
        e.satir - oncekiSatir, e.sutun - oncekiSutun];
      if (e.ad !== null) alanlar.push(e.ad - oncekiAd);
      oncekiUretilenSutun = e.uretilenSutun;
      oncekiKaynak = e.kaynak; oncekiSatir = e.satir; oncekiSutun = e.sutun;
      if (e.ad !== null) oncekiAd = e.ad;
      return alanlar.map(vlqKodla).join("");
    }).join(",");
  }).join(";");
}

const harita = {
  version: 3,
  file: "olcum.min.js",
  sources: ["olcum.js"],
  names: ["olcumleriCiz", "satirKur"],
  mappings: mappingsUret(ESLEMELER),
};
console.log("mappings:", harita.mappings);

// Cozme: fark dizisinden saltik konumlara geri don.
function mappingsCoz(mappings) {
  let kaynak = 0, satir = 0, sutun = 0, ad = 0;
  return mappings.split(";").map((satirMetni) => {
    let uretilenSutun = 0;
    if (satirMetni === "") return [];
    return satirMetni.split(",").map((bolum) => {
      const a = vlqCoz(bolum);
      uretilenSutun += a[0]; kaynak += a[1]; satir += a[2]; sutun += a[3];
      if (a.length === 5) ad += a[4];
      return { uretilenSutun, kaynak, satir, sutun, ad: a.length === 5 ? ad : null };
    });
  });
}

const cozulen = mappingsCoz(harita.mappings);

// Uretilen konumdan kaynaga: satirdaki, sutunu gecmeyen SON esleme alinir.
function kaynaktaBul(uretilenSatir, uretilenSutun) {
  const satirEslemeleri = cozulen[uretilenSatir] ?? [];
  let bulunan = null;
  for (const e of satirEslemeleri) if (e.uretilenSutun <= uretilenSutun) bulunan = e;
  if (!bulunan) return null;
  return {
    dosya: harita.sources[bulunan.kaynak],
    satir: bulunan.satir + 1,
    sutun: bulunan.sutun + 1,
    ad: bulunan.ad === null ? null : harita.names[bulunan.ad],
  };
}

console.log("\nuretilen konum → kaynaktaki konum");
for (const [s, k] of [[0, 0], [0, 20], [0, 40], [1, 3], [1, 12]]) {
  const c = kaynaktaBul(s, k);
  console.log(`  olcum.min.js ${s + 1}:${k + 1}`.padEnd(24) +
    `→ ${c.dosya} ${c.satir}:${c.sutun}` + (c.ad ? `  (${c.ad})` : ""));
}
```

```
mappings: AAAAA,YAIEC,mBACY;AAId,QAEI

uretilen konum → kaynaktaki konum
  olcum.min.js 1:1      → olcum.js 1:1  (olcumleriCiz)
  olcum.min.js 1:21     → olcum.js 5:3  (satirKur)
  olcum.min.js 1:41     → olcum.js 6:15
  olcum.min.js 2:4      → olcum.js 10:1
  olcum.min.js 2:13     → olcum.js 12:5
```

Çıktı üç ayrıntıyı görünür kılar.

Birincisi, **eşleme tam değildir**. Yirmi birinci sütun için tabloda kayıt yoktur; çözüm,
o sütunu geçmeyen son eşlemeye düşer. Hata ayıklayıcıda bir kesme noktasının yazıldığı
satırdan biraz kayması bundandır: en yakın eşlemeye tutunur.

İkincisi, **ad alanı isteğe bağlıdır**. Üçüncü eşlemede ad yoktur; kısaltılmış bir
değişkenin özgün adı yalnızca eşlemesi ad taşıyorsa geri getirilebilir. Kapsam görünümünde
tek harfli adlar görünmeye devam ediyorsa nedeni budur.

Üçüncüsü, **kodlama farklara dayanır**. Bir bölümün çözülmesi kendinden öncekilere
bağlıdır; tablonun ortasından okumaya başlanamaz. Bu, dosyayı küçük tutar ve tam çözümü
zorunlu kılar.

#### Haritanın Dağıtımı

Kaynak haritası ayrı bir dosyadır ve üretilen dosyanın sonundaki bir yorum satırıyla ona
bağlanır. Dağıtım kararı üç seçeneğe iner.

**Haritayı herkese açık sunmak** en kolayıdır ve kaynak kodunu okunabilir kılar. Kapalı
kaynak bir uygulamada bu, kodun yayımlanması anlamına gelir.

**Haritayı üretmek ama yayımlamamak** yaygın orta yoldur: harita hata izleme sistemine
yüklenir, sunucudan servis edilmez. Kullanıcının tarayıcısında yığıt izi kısaltılmış
hâliyle kalır, geliştirici tarafında çözülmüş hâliyle görünür.

**Hiç üretmemek** ölçülebilir bir kazanç sağlamaz — harita yalnızca istendiğinde
indirilir — ve üretimdeki hataların tanısını olanaksız kılar.

Haritanın kaynak metni kendi içinde taşıyıp taşımayacağı da ayrı bir karardır: taşırsa
dosya büyür ama özgün dosyalara ayrıca erişim gerekmez; taşımazsa harita küçüktür ve
kaynakların ayrıca sunulması gerekir.

#### Özet

- Kesme noktası ana iş parçacığını durdurur; zamana bağlı davranış duraklatmayla bozulur,
  bu yüzden zamanlama sorunları kayıtla incelenir.
- Kesme noktası türü sorulan soruya göre seçilir: satır, koşul, kayıt, istisna, ağaç
  değişimi, olay ve istek.
- Çağrı yığıtı o noktaya nasıl gelindiğini, kapsam görünümü seçilen çerçevedeki değerleri
  verir; eşzamansız sınırlar yığıta ayrıca eklenir.
- Adımlama üç kiptir; yardımcı dosyaların yok sayılması adımlamayı kendi kodunda tutar.
- Kaynak haritası, farklarla kodlanmış bir eşleme tablosudur; çözüm en yakın eşlemeye
  düşer ve ad alanı isteğe bağlıdır.
- Harita dağıtımı, kodun okunabilirliği ile üretim hatalarının tanınabilirliği arasında bir
  karardır.

#### Kurs Kapanışı

Bu kurs tarayıcıyı bir çalışma ortamı olarak ele aldı ve dört katmanda kurdu.

**Belge ve olaylar** katmanında sayfa, program içinden okunup değiştirilebilen bir veri
yapısına dönüştü; olayın kökten hedefe ve hedeften köke izlediği tanımlı yol, tek
dinleyiciyle çok ögeyi yönetmeyi, tarayıcının varsayılan davranışına müdahale etmeyi ve
klavye ile form etkileşimini kurmayı sağladı.

**Tarayıcı API'leri** katmanı sayfaya kalıcılık, gezinme denetimi, gözlem yeteneği,
kareyle uyumlu güncelleme, yerelleştirilmiş biçimleme ve ana iş parçacığını boşaltma
olanağı ekledi.

**Web bileşenleri ve çevrimdışı** katmanı iki bağımsızlık kurdu: arayüz parçasının
sayfadan bağımsızlığı — kendi adı, yaşam döngüsü, kapsüllenmiş yapısı ve biçemiyle — ve
uygulamanın ağdan bağımsızlığı — araya giren bir çalışan, kaynak başına verilmiş önbellek
kararları ve kurulabilir bir kabukla.

**Geliştirici araçları** katmanı bu üçünün üzerine tanıyı koydu. Her aracın yanıtladığı
soru ayrıydı: hangi kural kazandı, istek nereden ve ne kadar sürede karşılandı, ana iş
parçacığı nerede tıkandı, hangi nesne bırakılmadı, o satırda değer neydi. Ortak yöntem her
seferinde aynıydı — önce soruyu belirlemek, sonra o soruyu yanıtlayan ölçüyü okumak.

Bu kursun kurduğu her şey **elle** yapılıyordu: düğüm oluşturmak, dinleyici bağlamak,
değişikliği ilgili ögeye yazmak, sökerken hepsini geri almak. Ölçüm rozeti bir bileşen
oldu, ama panodaki on beş rozetin listeyle eşzamanlı kalması hâlâ elle yazılmış
güncelleme koduna bağlı. Liste bir kayıt eklendiğinde hangi düğümlerin değişeceğini kodun
kendisi hesaplıyor; bu hesap büyüdükçe hata kaynağına dönüşüyor.

Bir sonraki kurs, **M14/K05 · Bileşen Tabanlı Arayüz Geliştirme**, bu hesabı tersine
çevirir. Arayüz, adım adım nasıl değiştirileceğiyle değil, verili durumda **ne olması
gerektiğiyle** tanımlanır; aradaki farkı bulmak ve ağaca uygulamak altyapının işi olur.
Platform yetenekleri ve tanı araçları kurulduğuna göre sıra, bildirimsel arayüz modelinde
ve bileşenlerin bileşiminde.

