Dans JavaScript ES6, quelle est la différence entre un itérable et un itérateur?

14

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 , qu'un itérable est un objet, disons, objtel qu'il obj[Symbol.iterator]fait référence à une fonction, de sorte que lorsqu'il est invoqué, retourne un objet qui a une nextméthode qui peut retourner un {value: ___, done: ___}objet:

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)

Donc, dans le code ci-dessus, barest l'itérable, et wahest l'itérateur, et next()est l'interface d'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, gen1est le générateur et iter1l'itérateur, et iter1.next()fera le bon travail. Mais iter1[Symbol.iterator]donne une fonction qui, lorsqu'elle est invoquée, donne en retour iter1, qui est un itérateur. iter1Est donc à la fois un itérable et un itérateur dans ce cas?

En outre, iter1est 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 iter1est un itérable, mais puisqu'il retourne 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 itérable bar, combien de fois peut [...bar]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?

non-polarité
la source
" Alors, iter1est-ce à la fois un itérable et un itérateur dans ce cas? " - oui. Tous les itérateurs natifs sont également itérables en se retournant, de sorte que vous pouvez facilement les passer dans des constructions qui attendent un itérable.
Bergi
Duplication possible de Différence entre Iterator et Iterable
Felix Kling

Réponses:

10

Oui, iterables et itérateurs sont des choses différentes, mais la plupart des itérateurs (y compris tous ceux que vous obtenez de JavaScript lui - même, comme des keysou valuesméthodes sur Array.prototypeou générateurs de fonctions du générateur) Hériter de l' objet% IteratorPrototype% de , qui a une Symbol.iteratorméthode comme cette:

[Symbol.iterator]() {
    return this;
}

Le résultat est que tous les itérateurs standard sont également des itérables. C'est ainsi que vous pouvez les utiliser directement, ou les utiliser dans des for-ofboucles et autres (qui attendent des itérables, pas des itérateurs).

Considérez la keysméthode des tableaux: il retourne 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-ofprend un itérable , pas un itérateur , alors pourquoi ça marche?

Cela fonctionne parce que l'itérateur est également itérable; Symbol.iteratorrevient juste 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 slicepour couper le sous-ensemble, vous pouvez obtenir l'itérateur, lire la première valeur, puis passez à une for-ofboucle:

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 que ce sont tous des itérateurs standard . Parfois, les gens montrent des exemples d'itérateurs codés manuellement comme celui-ci:

L'itérateur retourné par rangelà n'est pas un itérable, donc il échoue lorsque nous essayons de l'utiliser avec for-of.

Pour le rendre itérable, nous devons:

  1. Ajoutez-y la Symbol.iteratorméthode au début de la réponse ci-dessus, ou
  2. Faites-le hériter de% IteratorPrototype%, qui a déjà cette méthode

Malheureusement, 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:


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.

TJ Crowder
la source
0

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;
}

fooest appelé fonction de générateur . Et puis quand nous avons

let bar = foo();

barest un objet générateur . Et un 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 .next()méthode.

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 puissions également 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 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ératif et au protocole itérateur.

S'il s'agit simplement d'un objet conforme au protocole itérateur , vous ne pouvez pas utiliser [...iter]ou for (a of iter). Il doit s'agir d'un objet conforme au protocole itérable .

Et puis, il y a aussi une nouvelle classe Iterator, dans une future spécification JavaScript qui est toujours en projet . Il dispose d' une interface plus grande, y compris des méthodes telles que forEach, map, reducede l'interface Array en cours et nouveaux, tels que et take, et drop. L'itérateur actuel fait référence à l'objet avec juste l' nextinterface.

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 objtel qu'il 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.

non-polarité
la source