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, yineleyicinextsunar. for...of, yayma, ayrıştırma,Array.fromve 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.
breakve erken ayrıştırma, yineleyicininreturnyö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.