İçeriğe geç
Educora
İleri20 dk18 / 27

Yineleyiciler, üreteçler ve semboller

`for...of` ve spread'in nasıl çalıştığını öğren: yineleme protokolü, `Symbol.iterator`, `yield` ile üreteç fonksiyonları, tembel diziler ve benzersiz anahtar olarak semboller.

Kendini test et
Bu derste öğreneceklerin
  • Yineleme protokolünü (Symbol.iterator, next(), { value, done }) açıklamak ve kendi nesneni yinelenebilir yapmak
  • Tembel ve sonsuz diziler için yield ile üreteç fonksiyonları yazmak
  • Sembolleri çakışmayan benzersiz anahtarlar olarak kullanmak ve iyi bilinen sembolleri tanımak

for...of dizilerle, string'lerle, Map ve Set ile çalışır, ama sıradan bir {} nesnesiyle çalışmaz; TypeError: ... is not iterable alırsın. Dizilerde ve string'lerde olup nesnelerde olmayan şey nedir? Symbol.iterator anahtarı altındaki özel bir metot. Bu protokolü öğrenince herhangi bir nesneyi for...of, spread ve destructuring ile çalışır hâle getirebilirsin; üreteçler ise bunu neredeyse zahmetsiz yapar.

Semboller

Symbol, ES2015 ile gelen bir ilkel tiptir (JavaScript'te toplam yedi ilkel tip vardır). Her Symbol() çağrısı, açıklaması aynı olsa bile benzersiz bir değer oluşturur. Semboller, başka anahtarlarla asla çakışmayan özellik anahtarları olarak kullanılır: bir kütüphane nesnene kendi sembol anahtarını eklerse senin id ya da name özelliğine dokunmaz. Sembol anahtarları for...in, Object.keys ve JSON.stringify içinde görünmez. Symbol.for('key') ise tüm program için ortak bir sembol döndürür.

JavaScript
const id1 = Symbol('id');
const id2 = Symbol('id');

console.log(typeof id1, id1 === id2);
console.log(id1.toString(), id1.description);

const user = { name: 'Leyla', [id1]: 501 };
console.log(user[id1]);
console.log(Object.keys(user), JSON.stringify(user));

console.log(Symbol.for('app') === Symbol.for('app'));
▸ Beklenen çıktı
symbol false
Symbol(id) id
501
[ 'name' ] {"name":"Leyla"}
true
İyi bilinen sembolNeyi kontrol eder
Symbol.iteratorfor...of, spread, destructuring
Symbol.asyncIteratorfor await...of
Symbol.toPrimitivenesnenin sayıya ya da string'e dönüşmesi
Symbol.toStringTagObject.prototype.toString sonucundaki ad

Yineleme protokolü

Bir nesnenin bir yineleyici (iterator) döndüren [Symbol.iterator]() metodu varsa nesne yinelenebilirdir (iterable). Yineleyici, next() metodu olan bir nesnedir; her next() çağrısı { value, done } döndürür. done: true, dizinin bittiğini bildirir. for...of, spread ..., destructuring, Array.from, new Map(...) ve Promise.all hepsi bu protokolü kullanır.

JavaScript
const letters = ['a', 'b'];
const iterator = letters[Symbol.iterator]();

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
▸ Beklenen çıktı
{ value: 'a', done: false }
{ value: 'b', done: false }
{ value: undefined, done: true }
Perde arkasında for...of tam olarak bunu yapar: done: true gelene kadar next() çağırır.
JavaScript
const range = {
  from: 1,
  to: 5,
  [Symbol.iterator]() {
    let current = this.from;
    const last = this.to;
    return {
      next() {
        return current <= last
          ? { value: current++, done: false }
          : { value: undefined, done: true };
      },
    };
  },
};

for (const n of range) console.log(n);
console.log([...range], Math.max(...range));
const [first, second] = range;
console.log(first, second);
▸ Beklenen çıktı
1
2
3
4
5
[ 1, 2, 3, 4, 5 ] 5
1 2

Üreteçler

Bir yineleyiciyi elle yazmak uzun iştir. Bir üreteç fonksiyonu (function*) çağrıldığında gövdesini çalıştırmaz; hem yineleyici hem yinelenebilir olan bir üreteç nesnesi döndürür. yield bir değer verir ve fonksiyonu duraklatır; sonraki next() çalışmayı tam yield'in arkasından sürdürür. Kod tembel çalışır: bir değer yalnızca istendiğinde hesaplanır.

JavaScript
function* countdown(from) {
  console.log('start');
  while (from > 0) {
    yield from;
    from--;
  }
  return 'liftoff';
}

const gen = countdown(3);
console.log('generator created');
console.log(gen.next());
console.log(gen.next().value, gen.next().value);
console.log(gen.next());
console.log([...countdown(2)]);
▸ Beklenen çıktı
generator created
start
{ value: 3, done: false }
2 1
{ value: 'liftoff', done: true }
start
[ 2, 1 ]
“start” yalnızca ilk next()'te yazdırılır. return değeri yalnızca done: true ile birlikte gelir; for...of ve spread onu yok sayar.

Değerler istek üzerine hesaplandığı için bir üreteç sonsuz olabilir: burada while (true) güvenlidir, çünkü take yalnızca gereken sayıda değer alır ve durur. Bu, büyük dosyaları satır satır okumak ya da sayfa sayfa gelen veriler için de kullanışlıdır.

JavaScript
function* ids() {
  let n = 1;
  while (true) yield `id-${n++}`;
}

function* fibonacci() {
  let [a, b] = [0, 1];
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

function* take(iterable, count) {
  if (count <= 0) return;
  for (const item of iterable) {
    yield item;
    if (--count === 0) return;
  }
}

console.log([...take(ids(), 3)]);
console.log([...take(fibonacci(), 10)].join(' '));
▸ Beklenen çıktı
[ 'id-1', 'id-2', 'id-3' ]
0 1 1 2 3 5 8 13 21 34

Uygulamada üreteçler

JavaScript
class Playlist {
  #songs = [];

  add(...titles) {
    this.#songs.push(...titles);
    return this;
  }

  *[Symbol.iterator]() {
    yield* this.#songs;
  }
}

const playlist = new Playlist().add('Morning', 'Caspian Waves', 'Old Baku');

for (const [index, song] of [...playlist].entries()) {
  console.log(`${index + 1}. ${song}`);
}
console.log(Array.from(playlist, (song) => song.length));
▸ Beklenen çıktı
1. Morning
2. Caspian Waves
3. Old Baku
[ 7, 13, 8 ]
yield* işi başka bir yinelenebilire, burada gizli diziye devreder. Şarkılar gizli kalır, ama sınıf for...of ve Array.from ile çalışır.

Veriler zamanla geliyorsa (bir API'nin sayfaları, sohbet mesajları) asenkron üreteç (async function*) ve for await...of döngüsü işe yarar: döngü her bir sonraki parçayı bekler.

JavaScript
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function* loadPages(total) {
  for (let page = 1; page <= total; page++) {
    await wait(100);
    yield [`item ${page * 2 - 1}`, `item ${page * 2}`];
  }
}

for await (const items of loadPages(3)) {
  console.log(items);
}
console.log('All pages loaded');
▸ Beklenen çıktı
[ 'item 1', 'item 2' ]
[ 'item 3', 'item 4' ]
[ 'item 5', 'item 6' ]
All pages loaded
Alıştırma

chunk(array, size) üretecini yaz: diziyi size elemanlık parçalara bölsün ve her parçayı yield etsin (son parça daha kısa olabilir).

Alıştırma · JavaScript
function* chunk(array, size) {
  // yield slices of the array with `size` items each
}

for (const part of chunk([1, 2, 3, 4, 5, 6, 7], 3)) {
  console.log(part);
}
console.log([...chunk(['a', 'b', 'c', 'd'], 2)].length);
▸ Beklenen çıktı
[ 1, 2, 3 ]
[ 4, 5, 6 ]
[ 7 ]
2
Alıştırma

countdown nesnesini üreteç kullanmadan yinelenebilir yap: [Symbol.iterator]() metodu, start'tan 1'e kadar sayıları veren bir next() metoduna sahip nesne döndürsün. Her döngü yeni bir yineleyici almalı.

Alıştırma · JavaScript
const countdown = {
  start: 3,
  // add [Symbol.iterator]() that returns { next() { ... } }
};

console.log([...countdown]);
for (const n of countdown) {
  console.log(`T-${n}`);
}
▸ Beklenen çıktı
[ 3, 2, 1 ]
T-3
T-2
T-1

Önemli noktalar

  • Sembol benzersiz bir ilkeldir; sembol anahtarları çakışmaz ve Object.keys ile JSON.stringify içinde görünmez.
  • Yinelenebilir bir nesnenin yineleyici döndüren bir [Symbol.iterator]() metodu vardır; yineleyicinin next() metodu { value, done } döndürür.
  • for...of, spread, destructuring ve Array.from her yinelenebilirle çalışır.
  • function* ve yield duraklatılabilen, tembel, hatta sonsuz üreteçler oluşturur.
  • async function* ve for await...of, zamanla gelen veriler içindir.

Kendini test et

10 soru. Her doğru cevap XP kazandırır.

1 / 10
Symbol('x') === Symbol('x') neye eşittir?