Iterovatelné objekty jsou zobecnÄnÃm polÃ. Je to koncept, který nám umožÅuje uÄinit kterýkoli objekt použitelným v cyklu for..of.
JistÄ, pole jsou iterovatelná. Existuje vÅ¡ak mnoho dalÅ¡Ãch vestavÄných objektů, které jsou rovnÄž iterovatelné. NapÅÃklad ÅetÄzce jsou také iterovatelné.
Jestliže objekt nenà technicky pole, ale pÅedstavuje kolekci (seznam, množinu) nÄjakých prvků, pak je for..of skvÄlá syntaxe, jak tyto prvky procházet. PodÃvejme se tedy, jak ji zprovoznit.
Symbol.iterator
Koncept iterovatelných objektů můžeme snadno pochopit tak, že si vytvoÅÃme vlastnÃ.
NapÅÃklad máme objekt, který sice nenà pole, ale zdá se být vhodný pro for..of.
TÅeba objekt rozsah, který pÅedstavuje interval ÄÃsel:
let rozsah = {
zaÄátek: 1,
konec: 5
};
// Chceme, aby for..of fungovalo:
// for(let ÄÃslo of rozsah) ... ÄÃslo=1,2,3,4,5
Abychom uÄinili objekt rozsah iterovatelným (a tÃm zprovoznili for..of), musÃme do tohoto objektu pÅidat metodu nazvanou Symbol.iterator (speciálnà vestavÄný symbol právÄ pro tento úÄel).
- Když
for..ofzaÄne, jedenkrát tuto metodu zavolá (nebo ohlásà chybu, nenÃ-li nalezena). Metoda musà vracet iterátor â objekt obsahujÃcà metodunext. - Nadále
for..ofpracuje pouze s tÃmto vráceným objektem. - Když
for..ofchce dalšà hodnotu, volá na tomto objektunext(). - Výsledek
next()musà mÃt tvar{done: Boolean, value: cokoli}, kdedone=trueznamená, že cyklus má skonÄit, v opaÄném pÅÃpadÄ jevaluejeho dalšà hodnotou.
Zde je úplná implementace objektu rozsah s komentáÅi:
let rozsah = {
zaÄátek: 1,
konec: 5
};
// 1. volánà for..of nejprve zavolá tuto funkci
rozsah[Symbol.iterator] = function() {
// ...tato funkce vrátà objekt iterátoru:
// 2. Od této chvÃle for..of pracuje jen s nÞe uvedeným objektem iterátoru a ptá se ho na dalšà hodnoty
return {
aktuálnÃ: this.zaÄátek,
poslednÃ: this.konec,
// 3. next() je volána cyklem for..of pÅi každé iteraci
next() {
// 4. mÄla by vrátit hodnotu jako objekt {done:..., value :...}
if (this.aktuálnà <= this.poslednÃ) {
return { done: false, value: this.aktuálnÃ++ };
} else {
return { done: true };
}
}
};
};
// teÄ to funguje!
for (let ÄÃslo of rozsah) {
alert(ÄÃslo); // 1, pak 2, 3, 4, 5
}
ProsÃme vÅ¡imnÄte si důležité vlastnosti iterovatelných objektů: jednotlivé záležitosti jsou oddÄleny.
- Sám objekt
rozsahnemá metodunext(). - MÃsto toho se volánÃm
rozsah[Symbol.iterator]()vytvoÅà jiný objekt, tzv. âiterátorâ, a hodnoty pro iteraci generuje jeho metodanext().
Objekt iterátoru je tedy oddÄlen od objektu, nad nÃmž se iteruje.
Technicky je můžeme spojit a použÃt jako iterátor samotný rozsah, abychom kód zjednoduÅ¡ili.
TÅeba takto:
let rozsah = {
zaÄátek: 1,
konec: 5,
[Symbol.iterator]() {
this.aktuálnà = this.zaÄátek;
return this;
},
next() {
if (this.aktuálnà <= this.konec) {
return { done: false, value: this.aktuálnÃ++ };
} else {
return { done: true };
}
}
};
for (let ÄÃslo of rozsah) {
alert(ÄÃslo); // 1, pak 2, 3, 4, 5
}
Nynà rozsah[Symbol.iterator]() vrátà samotný objekt rozsah: ten obsahuje potÅebnou metodu next() a pamatuje si aktuálnà krok iterace v this.aktuálnÃ. Je to kratÅ¡Ã? Ano. A nÄkdy je to i vhodné.
Nevýhodou je, že nynà nemůžeme mÃt dva cykly for..of, které budou nad tÃmto objektem probÃhat souÄasnÄ: sdÃlely by stav iterace, protože iterátor je pouze jeden â samotný objekt. Ale dva paralelnà cykly for..of jsou vzácnostÃ, dokonce i v asynchronnÃch scénáÅÃch.
Je možné vytváÅet i nekoneÄné iterátory. NapÅÃklad rozsah se stane nekoneÄným pro rozsah.konec = Infinity. Nebo můžeme vytvoÅit iterovatelný objekt, který bude generovat nekoneÄnou posloupnost pseudonáhodných ÄÃsel. I ten může být užiteÄný.
Na metodu next nejsou kladena žádná omezenÃ, může vracet dalšà a dalšà hodnoty, to je normálnÃ.
SamozÅejmÄ cyklus for..of nad takovým iterovatelným objektem by byl nekoneÄný. Vždy ho vÅ¡ak můžeme zastavit pÅÃkazem break.
ÅetÄzec je iterovatelný
NejÄastÄji použÃvané iterovatelné objekty jsou pole a ÅetÄzce.
U ÅetÄzce cyklus for..of procházà jeho znaky:
for (let znak of "test") {
// spustà se 4-krát: pro každý znak jednou
alert( znak ); // t, pak e, pak s, pak t
}
Funguje to korektnÄ i se zástupnými páry!
let ÅetÄzec = 'ð³ð';
for (let znak of ÅetÄzec) {
alert( znak ); // ð³, a pak ð
}
Explicitnà volánà iterátoru
Abychom tomu hloubÄji porozumÄli, podÃváme se, jak použÃt iterátor explicitnÄ.
Budeme iterovat nad ÅetÄzcem pÅesnÄ stejným způsobem jako for..of, ale pÅÃmými volánÃmi. Tento kód vytvoÅà ÅetÄzcový iterátor a zÃská z nÄj hodnoty âruÄnÄâ:
let ÅetÄzec = "Ahoj";
// provádà totéž jako
// for (let znak of ÅetÄzec) alert(znak);
let iterator = ÅetÄzec[Symbol.iterator]();
while (true) {
let výsledek = iterator.next();
if (výsledek.done) break;
alert(výsledek.value); // vypÃÅ¡e znaky jeden po druhém
}
Tento pÅÃstup potÅebujeme jen vzácnÄ, ale dává nám vÃce kontroly nad procesem než for..of. NapÅÃklad můžeme proces iterace rozdÄlit: trochu iterovat, pak to pÅeruÅ¡it, udÄlat nÄco jiného a pak iteraci obnovit.
Iterovatelné objekty a objekty podobné polÃm
Tyto dva oficiálnà pojmy vypadajà podobnÄ, ale jsou zcela odliÅ¡né. ProsÃme ujistÄte se, že jim dobÅe rozumÃte, abyste pÅedeÅ¡li zmatkům.
- Iterovatelné objekty jsou objekty, které implementujà metodu
Symbol.iterator, jak je popsáno výše. - Objekty podobné polÃm (array-like) jsou objekty, které majà indexy a vlastnost
length(délku), takže vypadajà jako pole.
Když použÃváme JavaScript pro praktické úkoly v prohlÞeÄi nebo v kterémkoli jiném prostÅedÃ, můžeme se setkat s objekty, které jsou iterovatelné, podobné polÃm, nebo obojÃ.
NapÅÃklad ÅetÄzce jsou jak iterovatelné (funguje na nich for..of), tak podobné polÃm (majà ÄÃselné indexy a vlastnost length).
Iterovatelný objekt však nemusà být podobný poli. A naopak, objekt podobný poli nemusà být iterovatelný.
NapÅÃklad rozsah ve výše uvedeném pÅÃkladu je iterovatelný, ale ne podobný poli, jelikož nemá indexované vlastnosti a length.
A zde je objekt, který je podobný poli, ale ne iterovatelný:
let objektPodobnýPoli = { // má indexy a length => je podobný poli
0: "Ahoj",
1: "svÄte",
length: 2
};
// Chyba (objekt neobsahuje Symbol.iterator)
for (let prvek of objektPodobnýPoli) {}
Jak iterovatelné objekty, tak objekty podobné polÃm zpravidla nejsou pole, tedy nemajà push, pop atd. To je ponÄkud nepohodlné, když takový objekt máme a chceme s nÃm pracovat jako s polem. NapÅÃklad bychom chtÄli pracovat s objektem rozsah pomocà metod polÃ. Jak toho docÃlit?
Array.from
Existuje univerzálnà metoda Array.from, která pÅijÃmá iterovatelnou nebo poli podobnou hodnotu a vytvoÅà z nà âopravdovéâ pole Array. Na tom pak můžeme volat metody polÃ.
PÅÃklad:
let objektPodobnýPoli = {
0: "Ahoj",
1: "svÄte",
length: 2
};
let pole = Array.from(objektPodobnýPoli); // (*)
alert(pole.pop()); // svÄte (metoda funguje)
Array.from na Åádku (*) vezme objekt, prozkoumá, zda je iterovatelný nebo podobný poli, a pak vyrobà nové pole a zkopÃruje do nÄj vÅ¡echny jeho prvky.
Pro iterovatelný objekt se dÄje totéž:
// pÅedpokládáme, že rozsah vezmeme z výše uvedeného pÅÃkladu
let pole = Array.from(rozsah);
alert(pole); // 1,2,3,4,5 (konverze pole pomocà toString funguje)
Ãplná syntaxe Array.from nám také umožÅuje poskytnout nepovinnou âmapovacÃâ funkci:
Array.from(obj[, mapFn, thisArg])
Nepovinný druhý argument mapFn může být funkce, která bude aplikována na každý prvek, než bude pÅidán do pole, a thisArg nám umožÅuje nastavit v této funkci this.
PÅÃklad:
// pÅedpokládáme, že rozsah vezmeme z výše uvedeného pÅÃkladu
// každé ÄÃslo umocnÃme na druhou
let pole = Array.from(rozsah, ÄÃslo => ÄÃslo * ÄÃslo);
alert(pole); // 1,4,9,16,25
Zde pomocà Array.from pÅetvoÅÃme ÅetÄzec na pole znaků:
let ÅetÄzec = 'ð³ð';
// rozdÄlà ÅetÄzec na pole znaků
let znaky = Array.from(ÅetÄzec);
alert(znaky[0]); // ð³
alert(znaky[1]); // ð
alert(znaky.length); // 2
Na rozdÃl od ÅetÄzec.split tato metoda využÃvá iterovatelnost ÅetÄzce, a proto, stejnÄ jako for..of, funguje korektnÄ se zástupnými páry.
Technicky zde provádà totéž jako:
let ÅetÄzec = 'ð³ð';
let znaky = []; // Array.from internÄ vykonává stejný cyklus
for (let znak of ÅetÄzec) {
znaky.push(znak);
}
alert(znaky);
â¦Ale je to kratÅ¡Ã.
Můžeme na nà dokonce postavit metodu slice, která bude rozpoznávat zástupné páry:
function slice(ÅetÄzec, zaÄátek, konec) {
return Array.from(ÅetÄzec).slice(zaÄátek, konec).join('');
}
let ÅetÄzec = 'ð³ðð©·¶';
alert( slice(ÅetÄzec, 1, 3) ); // ðð©·¶
// nativnà metoda nepodporuje zástupné páry
alert( ÅetÄzec.slice(1, 3) ); // nesmysly (dvÄ Äásti z různých zástupných párů)
ShrnutÃ
Objekty, které lze použÃt ve for..of, se nazývajà iterovatelné.
- Technicky musejà iterovatelné objekty implementovat metodu s názvem
Symbol.iterator.- Výsledek metody
obj[Symbol.iterator]()se nazývá iterátor. ÅÃdà dalšà iteraÄnà proces. - Iterátor musà obsahovat metodu jménem
next(), která vracà objekt{done: Boolean, value: cokoli}, v nÄmždone:trueoznamuje konec iteraÄnÃho procesu, jinakvalueje dalšà hodnota.
- Výsledek metody
- Metoda
Symbol.iteratorje cyklemfor..ofvolána automaticky, ale můžeme ji volat i pÅÃmo. - Metodu
Symbol.iteratorimplementujà i vestavÄné iterovatelné objekty, napÅ. ÅetÄzce nebo pole. - ÅetÄzcový iterátor rozpoznává zástupné páry.
Objekty, které majà indexované vlastnosti a vlastnost length, se nazývajà podobné polÃm. Takové objekty mohou mÃt i jiné vlastnosti a metody, ale postrádajà vestavÄné metody polÃ.
PodÃváme-li se do specifikace, uvidÃme, že vÄtÅ¡ina vestavÄných metod pÅedpokládá, že pracujà s iterovatelnými nebo polÃm podobnými objekty mÃsto âopravdovýchâ polÃ, protože je to abstraktnÄjÅ¡Ã.
Array.from(obj[, mapFn, thisArg]) vytvoÅà opravdové pole Array z iterovatelného nebo poli podobného objektu obj. Na nÄ pak můžeme použÃvat metody polÃ. Nepovinné argumenty mapFn a thisArg nám umožÅujà na každý prvek aplikovat funkci.
KomentáÅe
<code>, pro nÄkolik Åádků je obalte znaÄkou<pre>, pro vÃce než 10 Åádků vložte odkaz na pÃskoviÅ¡tÄ (plnkr, jsbin, codepenâ¦)