Un itérable est-il le même qu'un itérateur, ou sont-ils différents?
Il semble, d'après les spécifications , un itérable est un objet, disons, obj
, tel que obj[Symbol.iterator]
Fait référence à une fonction, de sorte que lorsqu'il est invoqué, retourne un objet qui a une méthode next
qui peut retourner un objet {value: ___, done: ___}
:
function foo() {
let i = 0;
const wah = {
next: function() {
if (i <= 2) return { value: (1 + 2 * i++), done: false }
else return { value: undefined, done: true }
}
};
return wah; // wah is iterator
}
let bar = {} // bar is iterable
bar[Symbol.iterator] = foo;
console.log([...bar]); // [1, 3, 5]
for (a of bar) console.log(a); // 1 3 5 (in three lines)
Ainsi, dans le code ci-dessus, bar
est l'itérable, et wah
est l'itérateur, et la next()
est l'interface de l'itérateur.
Donc, itérable et itérateur sont des choses différentes.
Maintenant, cependant, dans un exemple courant de générateur et d'itérateur:
function* gen1() {
yield 1;
yield 3;
yield 5;
}
const iter1 = gen1();
console.log([...iter1]); // [1, 3, 5]
for (a of iter1) console.log(a); // nothing
const iter2 = gen1();
for (a of iter2) console.log(a); // 1 3 5 (in three lines)
console.log(iter1[Symbol.iterator]() === iter1); // true
Dans le cas ci-dessus, gen1
Est le générateur, et iter1
Est l'itérateur, et iter1.next()
fera le travail approprié. Mais iter1[Symbol.iterator]
Donne une fonction qui, lorsqu'elle est invoquée, renvoie iter1
, Qui est un itérateur. Donc iter1
Est à la fois un itérable et un itérateur dans ce cas?
En outre, iter1
Est différent de l'exemple 1 ci-dessus, car l'itérable de l'exemple 1 peut donner [1, 3, 5]
Autant de fois que souhaité en utilisant [...bar]
, Tandis que iter1
est un itérable, mais puisqu'il revient lui-même, qui est le même itérateur à chaque fois, ne donnera [1, 3, 5]
qu'une seule fois.
Nous pouvons donc dire, pour un bar
itérable, combien de fois [...bar]
Peut donner le résultat [1, 3, 5]
- et la réponse est, cela dépend. Et est-il itérable comme un itérateur? Et la réponse est, ce sont des choses différentes, mais elles peuvent être les mêmes, lorsque l'itérable se sert lui-même d'itérateur. Est-ce exact?
Oui, les itérateurs et les itérateurs sont des choses différentes, mais la plupart des itérateurs (y compris toutes celles que vous obtenez à partir de JavaScript lui-même, telles que les méthodes keys
ou values
sur Array.prototype
ou générateurs de fonctions de générateur) héritent de objet% IteratorPrototype% , qui a un Symbol.iterator
méthode comme celle-ci:
[Symbol.iterator]() {
return this;
}
Le résultat est que tous les itérateurs standard sont également des itérables. Vous pouvez donc les utiliser directement ou les utiliser dans for-of
boucles et autres (qui attendent des itérables, pas des itérateurs).
Considérez la méthode keys
des tableaux: elle renvoie un itérateur de tableau qui visite les clés du tableau (ses index, sous forme de nombres). Notez qu'il retourne un itérateur . Mais une utilisation courante de celui-ci est:
for (const index of someArray.keys()) {
// ...
}
for-of
prend un itérable , pas un itérateur , alors pourquoi cela travail?
Cela fonctionne parce que l'itérateur est également itérable; Symbol.iterator
renvoie simplement this
.
Voici un exemple que j'utilise dans le chapitre 6 de mon livre: Si vous vouliez parcourir toutes les entrées mais sauter la première et que vous ne vouliez pas utiliser slice
pour couper le sous-ensemble, vous pouvez obtenir l'itérateur , lisez la première valeur, puis passez à un for-of
boucle:
const a = ["one", "two", "three", "four"];
const it = a[Symbol.iterator]();
// Skip the first one
it.next();
// Loop through the rest
for (const value of it) {
console.log(value);
}
Notez qu'il s'agit de tous itérateurs standard . Parfois, les gens montrent des exemples d'itérateurs codés manuellement comme celui-ci:
function range(start, end) {
let value = start;
let inc = start < end ? 1 : -1;
return {
next() {
const done = value == end;
const result = {done, value};
if (!done) {
value += inc;
}
return result;
}
};
}
// Works when used directly
const it = range(1, 5);
let result;
while (!(result = it.next()).done) {
console.log(result.value);
}
// Fails when an iterable is expected
try {
for (const value of range(1, 5)) {
console.log(value);
}
} catch (e) {
console.error(e.message);
}
L'itérateur renvoyé par range
il n'y a pas un itérable, donc il échoue lorsque nous essayons de l'utiliser avec for-of
.
Pour le rendre itérable, nous devons:
Symbol.iterator
méthode au début de la réponse ci-dessus, ouMalheureusement, TC39 a décidé de ne pas fournir un moyen direct d'obtenir l'objet% IteratorPrototype%. Il existe un moyen indirect (obtenir un itérateur à partir d'un tableau, puis prendre son prototype, qui est défini comme% IteratorPrototype%), mais c'est pénible.
Mais il n'est pas nécessaire d'écrire des itérateurs manuellement comme ça de toute façon; utilisez simplement une fonction de générateur, puisque le générateur qu'il renvoie est itérable:
function* range(start, end) {
let value = start;
let inc = start < end ? 1 : -1;
while (value !== end) {
yield value;
value += inc;
}
}
// Works when used directly
const it = range(1, 5);
let result;
while (!(result = it.next()).done) {
console.log(result.value);
}
// Also works when an iterable is expected
for (const value of range(1, 5)) {
console.log(value);
}
En revanche, tous les itérables ne sont pas des itérateurs. Les tableaux sont itérables, mais pas les itérateurs. Il en va de même pour les chaînes, les cartes et les ensembles.
J'ai trouvé qu'il existe des définitions plus précises des termes, et ce sont les réponses les plus définitives:
Selon les spécifications ES6 et MDN :
Quand nous avons
function* foo() { // note the "*"
yield 1;
yield 3;
yield 5;
}
foo
est appelé générateur fonction. Et puis quand nous avons
let bar = foo();
bar
est un générateur objet. Et n objet générateur est conforme à la fois au protocole itérable et au protocole itérateur .
La version la plus simple est l'interface de l'itérateur, qui n'est qu'une méthode .next()
.
Le protocole itérable est: pour l'objet obj
, obj[Symbol.iterator]
Donne une "fonction zéro argument qui renvoie un objet, conforme au protocole itérateur".
Par le titre du lien MDN , il semble également que nous pouvons aussi simplement appeler un objet générateur un "générateur".
Notez que dans le livre de Nicolas Zakas Understanding ECMAScript 6 , il a probablement appelé de manière lâche une "fonction de générateur" comme un "générateur", et un "objet générateur" comme un "itérateur". Le point à retenir est qu'ils sont vraiment tous deux liés au "générateur" - l'un est une fonction de générateur et l'autre est un objet générateur, ou générateur. L'objet générateur est conforme à la fois au protocole itérable et au protocole itérateur.
S'il s'agit simplement d'un objet conforme au protocole itérateur , vous ne pouvez pas utilisez [...iter]
ou for (a of iter)
. Il doit s'agir d'un objet conforme au protocole itérable .
Et puis, il y a aussi ne nouvelle classe Iterator, dans une future spécification JavaScript qui est encore en projet . Il a une plus grande interface, y compris des méthodes telles que forEach
, map
, reduce
de l'interface Array actuelle, et de nouvelles, telles que et take
, et drop
. L'itérateur actuel fait référence à l'objet avec juste l'interface next
.
Pour répondre à la question d'origine: quelle est la différence entre un itérateur et un itérable, la réponse est: un itérateur est un objet avec l'interface .next()
, et un itérable est un objet obj
tel que obj[Symbol.iterator]
peut donner une fonction à zéro argument qui, lorsqu'elle est invoquée, renvoie un itérateur.
Et un générateur est à la fois un itérable et un itérateur, pour ajouter à cela.