web-dev-qa-db-fra.com

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

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?

14
nonopolarity

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:

  1. Ajouter le Symbol.iterator mé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:

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.

10
T.J. Crowder

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.

0
nonopolarity