In JavaScript ES6, qual è la differenza tra un iterabile e un iteratore?


14

Un iterabile è lo stesso di un iteratore o sono diversi?

Sembra, dalle specifiche , che un iterabile sia un oggetto, diciamo, objtale che si obj[Symbol.iterator]riferisce a una funzione, così che quando viene invocato, restituisce un oggetto che ha un nextmetodo che può restituire un {value: ___, done: ___}oggetto:

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)

Quindi nel codice sopra, barè iterabile, wahè iteratore e next()è l'interfaccia iteratore.

Quindi, iterabile e iteratore sono cose diverse.

Ora, tuttavia, in un esempio comune di generatore e iteratore:

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

Nel caso sopra, gen1è il generatore ed iter1è l'iteratore e iter1.next()farà il lavoro giusto. Ma iter1[Symbol.iterator]dà una funzione che, quando viene invocata, restituisce iter1, che è un iteratore. Quindi iter1è sia un iterabile che un iteratore in questo caso?

Inoltre, iter1è diverso dall'esempio 1 sopra, perché l'iterabile nell'esempio 1 può dare [1, 3, 5]tutte le volte che si desidera utilizzare [...bar], mentre iter1è iterabile, ma poiché si restituisce da solo, che è lo stesso iteratore ogni volta, darà [1, 3, 5]una sola volta.

Quindi possiamo dire, per un iterabile bar, quante volte può [...bar]dare il risultato [1, 3, 5]- e la risposta è, dipende. Ed è iterabile lo stesso di un iteratore? E la risposta è che sono cose diverse, ma possono essere le stesse quando l'iterabile si utilizza come iteratore. È corretto?



" Quindi iter1sia un iterabile che un iteratore in questo caso? " - sì. Tutti gli iteratori nativi sono anche iterabili restituendo se stessi, in modo da poterli facilmente passare in costrutti che si aspettano un iterabile.
Bergi,

Risposte:


10

Sì, gli iterabili e gli iteratori sono cose diverse, ma la maggior parte degli iteratori (compresi tutti quelli che si ottengono dallo stesso JavaScript, ad esempio keysdall'o valuesmetodi Array.prototypeo dai generatori dalle funzioni del generatore) ereditano dall'oggetto % IteratorPrototype% , che ha un Symbol.iteratormetodo come Questo:

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

Il risultato è che tutti gli iteratori standard sono anche iterabili. È così che puoi usarli direttamente o usarli in for-ofloop e simili (che prevedono iterabili, non iteratori).

Considera il keysmetodo degli array: restituisce un iteratore di array che visita le chiavi dell'array (i suoi indici, come numeri). Si noti che restituisce un iteratore . Ma un uso comune è:

for (const index of someArray.keys()) {
    // ...
}

for-ofprende un iterabile , non un iteratore , quindi perché funziona?

Funziona perché anche l'iteratore è iterabile; Symbol.iteratorritorna e basta this.

Ecco un esempio che uso nel capitolo 6 del mio libro: se volevi slicescorrere tutte le voci ma salta la prima e non volevi usare per tagliare il sottoinsieme, puoi ottenere l'iteratore, leggere il primo valore, quindi passa a un for-ofciclo:

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

Si noti che si tratta di tutti gli iteratori standard . A volte le persone mostrano esempi di iteratori con codifica manuale come questa:

L'iteratore restituito da rangenon è un iterabile, quindi fallisce quando proviamo a usarlo con for-of.

Per renderlo iterabile, dovremmo:

  1. Aggiungi il Symbol.iteratormetodo all'inizio della risposta sopra, oppure
  2. Fallo ereditare da% IteratorPrototype%, che ha già quel metodo

Purtroppo, TC39 ha deciso di non fornire un modo diretto per ottenere l'oggetto% IteratorPrototype%. C'è un modo indiretto (ottenere un iteratore da un array, quindi prendere il suo prototipo, che è definito come% IteratorPrototype%), ma è una seccatura.

Ma non è necessario scrivere iteratori manualmente come quello comunque; basta usare una funzione generatore, poiché il generatore che restituisce è iterabile:


Al contrario, non tutti gli iterabili sono iteratori. Le matrici sono iterabili, ma non iteratori. Lo stesso vale per stringhe, mappe e set.


0

Ho scoperto che ci sono alcune definizioni più precise dei termini e queste sono le risposte più definitive:

Secondo le specifiche ES6 e MDN :

Quando abbiamo

function* foo() {   // note the "*"
    yield 1;
    yield 3;
    yield 5;
}

foosi chiama funzione generatore . E poi quando abbiamo

let bar = foo();

barè un oggetto generatore . E un oggetto generatore è conforme sia al protocollo iterabile che al protocollo iteratore .

La versione più semplice è l'interfaccia iteratore, che è solo un .next()metodo.

Il protocollo iterabile è: per l'oggetto obj, obj[Symbol.iterator]fornisce una "funzione zero argomenti che restituisce un oggetto, conforme al protocollo iteratore".

Dal titolo del collegamento MDN , sembra anche che possiamo anche semplicemente chiamare un oggetto generatore un "generatore".

Si noti che nel libro di Nicolas Zakas Understanding ECMAScript 6 , probabilmente ha vagamente definito una "funzione generatore" come "generatore" e un "oggetto generatore" come "iteratore". Il punto da asporto è che sono in realtà entrambi correlati al "generatore": uno è una funzione generatore e uno è un oggetto generatore o generatore. L'oggetto generatore è conforme sia al protocollo iterabile che al protocollo iteratore.

Se è solo un oggetto conforme al protocollo iteratore , non è possibile utilizzare [...iter]o for (a of iter). Deve essere un oggetto conforme al protocollo iterabile .

E poi, c'è anche una nuova classe Iterator, nelle future specifiche JavaScript che è ancora in bozza . Ha un'interfaccia grande, compresi metodi come forEach, map, reducedell'interfaccia Array corrente, e quelli nuovi, come e take, e drop. L'attuale iteratore si riferisce all'oggetto con solo l' nextinterfaccia.

Per rispondere alla domanda originale: qual è la differenza tra un iteratore e un iterabile, la risposta è: un iteratore è un oggetto con l'interfaccia .next()e un iterabile è un oggetto objtale che obj[Symbol.iterator]può dare una funzione zero-topic che, quando viene invocato, restituisce un iteratore.

E un generatore è sia iterabile che iteratore, da aggiungere a quello.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.