İçeriğe geç
academia.sh

Ders 09 / 24

Gözlemciler

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

İçindekiler

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 üç işlemle sonlandırılır: tek bir hedefin izlenmesi bırakılır, bütün hedeflerin izlenmesi kesilir ya da henüz teslim edilmemiş kayıtlar elle alınır. 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.

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

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

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat