- Yineleme protokolünü (
Symbol.iterator,next(),{ value, done }) açıklamak ve kendi nesneni yinelenebilir yapmak - Tembel ve sonsuz diziler için
yieldile ü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.
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 sembol | Neyi kontrol eder |
|---|---|
Symbol.iterator | for...of, spread, destructuring |
Symbol.asyncIterator | for await...of |
Symbol.toPrimitive | nesnenin sayıya ya da string'e dönüşmesi |
Symbol.toStringTag | Object.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.
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 }for...of tam olarak bunu yapar: done: true gelene kadar next() çağırır.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.
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 ]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.
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
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.
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
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).
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
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ı.
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.keysileJSON.stringifyiçinde görünmez. - Yinelenebilir bir nesnenin yineleyici döndüren bir
[Symbol.iterator]()metodu vardır; yineleyicininnext()metodu{ value, done }döndürür. for...of, spread, destructuring veArray.fromher yinelenebilirle çalışır.function*veyieldduraklatılabilen, tembel, hatta sonsuz üreteçler oluşturur.async function*vefor await...of, zamanla gelen veriler içindir.
Kendini test et
10 soru. Her doğru cevap XP kazandırır.
Symbol('x') === Symbol('x') neye eşittir?