İçeriğe geç
academia.sh

Ders 14 / 17

Yineleyiciler

Yineleme protokolünün iki sözleşmesi, bir türü prototip üzerinden gezilebilir kılmak, yineleyici ile gezilebilir ayrımı, erken çıkışta temizlik ve tembel üretim.

İçindekiler

Önceki derste WeakMap üzerinde for...of denemesi hata verdi, oysa aynı döngü Map, Set, dizi ve dizgi üzerinde çalışıyor. Aradaki fark bir tür listesi değildir: for...of döngüsü, yayma işleci, ayrıştırma ve Array.from hepsi tek bir sözleşmeye bakar.

Yineleme protokolü (iteration protocol) iki sözleşmeden oluşur. Bir nesne [Symbol.iterator] adlı bir yöntem sunuyorsa gezilebilirdir (iterable); bu yöntem, next sunan bir yineleyici (iterator) döndürmelidir. Ders önce sözleşmeyi elle kurar, sonra ölçüm serisini bu sözleşmeye bağlar.

next Sözleşmesi

Yineleyici, tek bir yöntemi olan sıradan bir nesnedir. next çağrısı iki alanlı bir nesne döndürür: sıradaki değer ve dizinin bitip bitmediği.

function olcumYineleyicisi(degerler) {
  let sira = 0;
  return {
    next() {
      if (sira >= degerler.length) return { value: undefined, done: true };
      const deger = degerler[sira];
      sira += 1;
      return { value: deger, done: false };
    },
  };
}

const yineleyici = olcumYineleyicisi([21.4, 19.8, 25.1]);

console.log(JSON.stringify(yineleyici.next()));
console.log(JSON.stringify(yineleyici.next()));
console.log(JSON.stringify(yineleyici.next()));
console.log(JSON.stringify(yineleyici.next()));

const ikinci = olcumYineleyicisi([21.4, 19.8]);
let adim = ikinci.next();
const toplananlar = [];
while (!adim.done) {
  toplananlar.push(adim.value);
  adim = ikinci.next();
}
console.log(toplananlar.join(","));
console.log(typeof yineleyici[Symbol.iterator]);
{"value":21.4,"done":false}
{"value":19.8,"done":false}
{"value":25.1,"done":false}
{"done":true}
21.4,19.8
undefined

Konum bilgisi sira değişkeninde tutulur ve dışarıdan görünmez — Kapanışlar dersindeki kalıp burada da işler. Dördüncü çıktıda value alanının görünmemesi dizileştirmenin sonucudur: undefined değerli alanlar JSON.stringify çıktısına girmez.

while döngüsü, for...of döngüsünün elle yazılmış hâlidir. Son satır ise bu nesnenin neden for...of ile kullanılamayacağını söyler: next sunuyor ama [Symbol.iterator] sunmuyor, yani yineleyicidir ama gezilebilir değildir.

Bir Türü Gezilebilir Kılmak

İkinci sözleşme, bir nesnenin [Symbol.iterator] yöntemi sunmasıdır. Yöntem sınıf gövdesine yazıldığında prototipte durur ve tüm örnekler gezilebilir olur.

class OlcumSerisi {
  constructor(sensor, degerler) {
    this.sensor = sensor;
    this.degerler = degerler;
  }

  [Symbol.iterator]() {
    let sira = 0;
    const degerler = this.degerler;
    return {
      next() {
        if (sira >= degerler.length) return { value: undefined, done: true };
        const deger = degerler[sira];
        sira += 1;
        return { value: deger, done: false };
      },
    };
  }
}

const seri = new OlcumSerisi("S-01", [21.4, 19.8, 25.1]);

const toplananlar = [];
for (const deger of seri) toplananlar.push(deger);
console.log(toplananlar.join(","));

console.log([...seri].join(","));
console.log(Array.from(seri).length);

const [ilk, ikinci] = seri;
console.log(`${ilk} ${ikinci}`);

console.log(Math.max(...seri));
console.log(Object.hasOwn(seri, Symbol.iterator));
console.log(Object.hasOwn(OlcumSerisi.prototype, Symbol.iterator));
console.log(Object.keys(seri).join(","));
console.log(JSON.stringify(seri));
21.4,19.8,25.1
21.4,19.8,25.1
3
21.4 19.8
25.1
false
true
sensor,degerler
{"sensor":"S-01","degerler":[21.4,19.8,25.1]}

Tek bir yöntem eklendi; beş ayrı dil özelliği çalışır duruma geldi. for...of, yayma, Array.from, dizi ayrıştırması ve argüman yayma hepsi aynı sözleşmeyi kullanır.

Altıncı ve yedinci satırlar dersin ekseniyle bağı kurar: yöntem örnekte değil, OlcumSerisi.prototype üzerindedir. Gezilebilirlik nesneye ait bir bayrak değil, zincirde bulunan bir yöntemdir. Bu yüzden bir türü sonradan gezilebilir kılmak, prototipine bu yöntemi eklemekle mümkündür.

Simge anahtarlı olduğu için Object.keys çıktısında görünmez ve dizileştirmeye girmez; Özellik Tanımlayıcıları dersinde geçen simge anahtarların bu özelliği, protokol yöntemlerini veri alanlarından ayrı tutmayı sağlar.

Yineleyici ile Gezilebilir Ayrımı

İki sözleşme çoğu zaman aynı nesnede birleşir. Yerleşik yineleyiciler kendilerini döndüren bir [Symbol.iterator] yöntemi sunar; bu da onların hem yineleyici hem gezilebilir olmasını sağlar.

const degerler = [21.4, 19.8, 25.1];

const yineleyici = degerler[Symbol.iterator]();
console.log(typeof yineleyici.next);
console.log(typeof yineleyici[Symbol.iterator]);
console.log(yineleyici[Symbol.iterator]() === yineleyici);

console.log(JSON.stringify(yineleyici.next()));

const kalanlar = [];
for (const deger of yineleyici) kalanlar.push(deger);
console.log(kalanlar.join(","));

const bosDonus = [];
for (const deger of yineleyici) bosDonus.push(deger);
console.log(bosDonus.length);

const birinciTur = [];
for (const deger of degerler) birinciTur.push(deger);
const ikinciTur = [];
for (const deger of degerler) ikinciTur.push(deger);
console.log(birinciTur.length === ikinciTur.length);

const dizgi = "olcum";
console.log([...dizgi].join("-"));
const harita = new Map([["S-01", 21.4]]);
console.log([...harita][0].join("="));
function
function
true
{"value":21.4,"done":false}
19.8,25.1
0
true
o-l-c-u-m
S-01=21.4

Ayrımın pratik sonucu tükenmedir. Yineleyici üzerinde gezinmek onu tüketir: ilk for...of kalan iki değeri okur, ikincisi hiçbir şey bulamaz. Gezilebilir nesne ise her gezinmede yeni bir yineleyici üretir, bu yüzden defalarca gezilebilir.

Bu, bir işleve gezilebilir bir nesne mi yoksa yineleyici mi verildiğinin neden önemli olduğunu açıklar. İki kez gezmesi gereken bir işleve yineleyici verilirse ikinci geçiş boş döner; hata mesajı da oluşmaz.

Son iki satır protokolün ne kadar yaygın olduğunu gösterir. Dizgi üzerinde gezinmek kod birimi değil, kod noktası verir — Bilgisayarlar Nasıl Çalışır kursundaki Karakter Kodlamaları dersinde tanıtılan ayrım burada gözlenebilir. Map üzerinde gezinmek ise anahtar–değer çiftleri verir.

Erken Çıkış ve Temizlik

for...of döngüsü break ile sonlandırıldığında ya da bir hata yükseldiğinde, protokol yineleyicinin return yöntemini çağırır. Bu, açık kalan kaynakların kapatılması için ayrılmış noktadır.

const kutuk = [];

function kaynakYineleyicisi(degerler) {
  let sira = 0;
  let kapali = false;
  return {
    [Symbol.iterator]() {
      return this;
    },
    next() {
      if (kapali || sira >= degerler.length) return { value: undefined, done: true };
      const deger = degerler[sira];
      sira += 1;
      return { value: deger, done: false };
    },
    return(deger) {
      kapali = true;
      kutuk.push(`kaynak kapatildi, okunan=${sira}`);
      return { value: deger, done: true };
    },
  };
}

const kaynak = kaynakYineleyicisi([21.4, 19.8, 25.1, 18.2]);

for (const deger of kaynak) {
  if (deger > 20 && deger < 22) {
    console.log(`bulundu: ${deger}`);
    break;
  }
}

console.log(kutuk.join(" | "));
console.log(JSON.stringify(kaynak.next()));

const ikinciKaynak = kaynakYineleyicisi([21.4, 19.8]);
const [tekDeger] = ikinciKaynak;
console.log(tekDeger);
console.log(kutuk.length);
bulundu: 21.4
kaynak kapatildi, okunan=1
{"done":true}
21.4
2

break deyimi yalnızca döngüyü kırmaz, yineleyiciye de haber verir. Kapatılan yineleyici sonraki next çağrılarında bitmiş olarak yanıt verir. Son bölüm, aynı çağrının dizi ayrıştırmasında da yapıldığını gösterir: istenen sayıda değer alındıktan sonra kaynak kapatılır.

Bu davranış, dosya ve ağ akışları gibi kaynaklara bağlı yineleyicilerde önemlidir; erken çıkış, kaynağın açık kalmasına yol açmaz.

Tembel Üretim

Protokolün son özelliği, değerlerin istendikçe üretilmesidir. Kaynak dizi olsa bile, her değer üzerinde yapılan hesap yalnızca o değer okunduğunda çalışır.

let hesapSayisi = 0;

function kalibreliSeri(hamDegerler, kayma) {
  return {
    [Symbol.iterator]() {
      let sira = 0;
      return {
        [Symbol.iterator]() {
          return this;
        },
        next() {
          if (sira >= hamDegerler.length) return { value: undefined, done: true };
          hesapSayisi += 1;
          const deger = hamDegerler[sira] + kayma;
          sira += 1;
          return { value: Number(deger.toFixed(2)), done: false };
        },
      };
    },
  };
}

const seri = kalibreliSeri([21.4, 19.8, 25.1, 18.2, 30.0], -0.4);

console.log(hesapSayisi);

let ilkEsikUstu;
for (const deger of seri) {
  if (deger > 24) {
    ilkEsikUstu = deger;
    break;
  }
}

console.log(ilkEsikUstu);
console.log(hesapSayisi);

const hepsi = [...seri];
console.log(hepsi.join(","));
console.log(hesapSayisi);
0
24.7
3
21,19.4,24.7,17.8,29.6
8

Seri kurulduğunda hiçbir hesap yapılmamıştır. Eşiği aşan ilk değer bulunana kadar üç hesap çalışır; kalan iki değer hiç hesaplanmaz. Aynı seri baştan gezildiğinde beş hesap daha yapılır ve toplam sekize çıkar.

Aynı iş map ile yazılsaydı beş hesap baştan yapılırdı. Programlama Temelleri kursundaki Fonksiyonel Programlamaya Giriş dersinde tanıtılan tembel değerlendirme budur ve büyük ya da sonu olmayan dizilerde tek uygulanabilir yoldur.

Protokolü elle yazmanın maliyeti de bu örnekte görünür: konum, bitiş koşulu ve kendini döndüren [Symbol.iterator] yöntemi her seferinde tekrarlanır. Sonraki ders bu tekrarı ortadan kaldıran sözdizimi tanıtacak.

Özet

  • Yineleme protokolü iki sözleşmeden oluşur: gezilebilir nesne [Symbol.iterator] sunar, yineleyici next sunar.
  • for...of, yayma, ayrıştırma, Array.from ve argüman yayma aynı sözleşmeyi kullanır; tek bir yöntem eklemek hepsini çalıştırır.
  • Sınıf gövdesine yazılan [Symbol.iterator] prototipte durur; gezilebilirlik zincirde bulunan bir yöntemdir.
  • Yineleyici tükenir, gezilebilir nesne her gezinmede yeni yineleyici üretir; iki kez gezilmesi gereken yerde bu ayrım belirleyicidir.
  • break ve erken ayrıştırma, yineleyicinin return yöntemini çağırarak kaynak temizliğine olanak verir.
  • Değerler istendikçe üretilir; okunmayan değer hiç hesaplanmaz.

Sonraki Adım

Elle yazılan yineleyicilerde aynı üç parça sürekli tekrarlandı: konumu tutan değişken, bitiş koşulu ve kendini döndüren [Symbol.iterator] yöntemi. Dil, bu üçünü de kendisi üreten bir fonksiyon türü sunar — çalışmasını durdurup kaldığı yerden sürdürebilen fonksiyonlar. Sonraki ders üreteçleri ele alacak ve bu dersteki tembel seriyi birkaç satıra indirecek.

İ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