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?



" 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

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.


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.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.