Was ist in JavaScript ES6 der Unterschied zwischen einem Iterator und einem Iterator?

14

Ist ein Iterable dasselbe wie ein Iterator oder unterscheiden sie sich?

Es scheint, von den Vorgaben , ein iterable ein Objekt ist, sagen wir, ist obj, so dass obj[Symbol.iterator]auf eine Funktion verweist, so dass , wenn aufgerufen, gibt ein Objekt , das eine hat nextMethode , die eine Rückkehr kann {value: ___, done: ___}Objekt:

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)

Im obigen Code barist also die Iterierbarkeit und wahder Iterator und next()die Iteratorschnittstelle.

Iterable und Iterator sind also verschiedene Dinge.

Nun jedoch zu einem gängigen Beispiel für Generator und Iterator:

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

Im obigen Fall gen1ist der Generator und iter1der Iterator und iter1.next()erledigt die richtige Arbeit. Gibt iter1[Symbol.iterator]aber eine Funktion, die beim Aufrufen etwas zurückgibt iter1, was ein Iterator ist. So iter1ist sowohl ein iterable und Iterator in diesem Fall?

Außerdem iter1unterscheidet es sich von dem obigen Beispiel 1, weil das Iterable in Beispiel 1 [1, 3, 5]so oft geben kann, wie es gewünscht wird [...bar], während iter1es ein Iterable ist, aber da es sich selbst zurückgibt, das jedes Mal der gleiche Iterator ist, gibt es nur [1, 3, 5]einmal.

Wir können also für eine Iterable sagen, barwie oft [...bar]das Ergebnis erzielt werden kann [1, 3, 5]- und die Antwort ist, es hängt davon ab. Und ist iterierbar dasselbe wie ein Iterator? Und die Antwort ist, sie sind verschiedene Dinge, aber sie können gleich sein, wenn sich das Iterable selbst als Iterator verwendet. Ist das korrekt?

Unpolarität
quelle
" Also iter1ist in diesem Fall sowohl ein Iterator als auch ein Iterator? " - ja. Alle nativen Iteratoren können auch iteriert werden, indem sie sich selbst zurückgeben, sodass Sie sie problemlos an Konstrukte übergeben können, die eine Iterierbarkeit erwarten.
Bergi
Mögliches Duplikat des Unterschieds zwischen Iterator und Iterable
Felix Kling

Antworten:

10

Ja, Iterables und Iteratoren sind verschiedene Dinge, aber die meisten Iteratoren (einschließlich aller Iteratoren, die Sie von JavaScript selbst erhalten, z. B. von den Methoden keysoder valuesMethoden Array.prototypeoder Generatoren von Generatorfunktionen) erben von Objekt% IteratorPrototype% , das eine Symbol.iteratorMethode wie diese hat diese:

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

Das Ergebnis ist, dass alle Standarditeratoren auch iterabel sind. Auf diese Weise können Sie sie direkt oder in for-ofSchleifen und dergleichen verwenden (die iterable, keine Iteratoren erwarten).

Betrachten Sie die keysMethode von Arrays: Es wird ein Array-Iterator zurückgegeben, der die Schlüssel des Arrays (seine Indizes als Zahlen) besucht. Beachten Sie, dass ein Iterator zurückgegeben wird . Aber eine übliche Verwendung davon ist:

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

for-ofnimmt eine iterable , keinen Iterator . Warum funktioniert das?

Es funktioniert, weil der Iterator auch iterierbar ist. Symbol.iteratorkehrt gerade zurück this.

Hier ist ein Beispiel, das ich in Kapitel 6 meines Buches verwende: Wenn Sie alle Einträge durchlaufen, aber den ersten überspringen möchten sliceund die Teilmenge nicht abschneiden möchten , können Sie den Iterator abrufen, den ersten Wert lesen. dann an eine for-ofSchleife übergeben:

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

Beachten Sie, dass dies alles Standarditeratoren sind . Manchmal zeigen Leute Beispiele für manuell codierte Iteratoren wie diese:

Der von rangedort zurückgegebene Iterator ist nicht iterierbar, daher schlägt er fehl, wenn wir versuchen, ihn mit zu verwenden for-of.

Um es iterierbar zu machen, müssten wir entweder:

  1. Ergänzen Sie die Symbol.iterator Methode am Anfang der obigen Antwort hinzu, oder
  2. Lassen Sie es von% IteratorPrototype% erben, das diese Methode bereits hat

Leider hat TC39 beschlossen, keinen direkten Weg zum Abrufen des% IteratorPrototype% -Objekts bereitzustellen. Es gibt einen indirekten Weg (einen Iterator aus einem Array abrufen und dann seinen Prototyp nehmen, der als% IteratorPrototype% definiert ist), aber es ist ein Schmerz.

Aber es ist sowieso nicht nötig, solche Iteratoren manuell zu schreiben. Verwenden Sie einfach eine Generatorfunktion, da der zurückgegebene Generator iterierbar ist:


Im Gegensatz dazu sind nicht alle Iterables Iteratoren. Arrays sind iterierbar, aber keine Iteratoren. Dies gilt auch für Zeichenfolgen, Karten und Mengen.

TJ Crowder
quelle
0

Ich fand heraus, dass es einige genauere Definitionen der Begriffe gibt, und dies sind die endgültigeren Antworten:

Gemäß den ES6-Spezifikationen und dem MDN :

Wenn wir haben

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

foogenannt ist Generatorfunktion . Und dann, wenn wir haben

let bar = foo();

barist ein Generator - Objekt . Und ein Generator - Objekt Konform sowohl auf das iterable - Protokoll und das Iterator - Protokoll .

Die einfachere Version ist die Iterator-Schnittstelle, die nur eine ist .next() Methode ist.

Das iterierbare Protokoll lautet: für das Objekt obj,obj[Symbol.iterator] gibt ein „Null - Argumente Funktion, gibt ein Objekt, auf das Iterator - Protokoll konform“.

Durch den Titel des MDN-Links können wir ein Generatorobjekt auch einfach als "Generator" bezeichnen.

Beachten Sie, dass er in Nicolas Zakas 'Buch Understanding ECMAScript 6 wahrscheinlich lose eine "Generatorfunktion" als "Generator" und ein "Generatorobjekt" als "Iterator" bezeichnet hat. Der Take-Away-Punkt ist, dass beide wirklich "Generator" -bezogen sind - eine ist eine Generatorfunktion und eine ist ein Generatorobjekt oder Generator. Das Generatorobjekt entspricht sowohl dem iterierbaren Protokoll als auch dem Iteratorprotokoll.

Wenn es nur ein Objekt auf das konforme Iterator - Protokoll, Sie können nicht verwenden [...iter]oder for (a of iter). Es muss ein Objekt sein, das dem iterierbaren Protokoll entspricht.

Und dann gibt es noch eine neue Iterator-Klasse in zukünftigen JavaScript-Spezifikationen, die sich noch in einem Entwurf befindet . Es hat eine größere Oberfläche, einschließlich Verfahrens , wie zum Beispiel forEach, map, reducedes aktuellen Array - Schnittstelle und neuen, wie und take, und drop. Der aktuelle Iterator bezieht sich nur auf das Objekt mit der nextSchnittstelle.

Um die ursprüngliche Frage zu beantworten: Was ist der Unterschied zwischen einem Iterator und einer abzählbaren, lautet die Antwort: ein Iterator ist ein Objekt mit der Schnittstelle .next()und ein iterable ist ein Objekt , objso dass obj[Symbol.iterator]eine Null-Argument Funktion geben kann , dass, wenn sie aufgerufen wird , Gibt einen Iterator zurück.

Und ein Generator ist sowohl iterierbar als auch iteratorisch.

Unpolarität
quelle