İçeriğe geç
academia.sh

Ders 08 / 24

Geçmiş 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ı.

İçindekiler

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

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

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

İ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