Dieser Inhalt wurde automatisch aus dem Englischen übersetzt, und kann Fehler enthalten.Erfahre mehr über dieses Experiment.
Iteratoren und Generatoren
Iteratoren und Generatoren bringen das Konzept der Iteration direkt in die Kernsprache und bieten einen Mechanismus zur Anpassung des Verhaltens vonfor...of-Schleifen.
Für weitere Details siehe auch:
In diesem Artikel
Iteratoren
In JavaScript ist einIterator ein Objekt, das eine Sequenz definiert und möglicherweise einen Rückgabewert bei dessen Beendigung.
Speziell ist ein Iterator jedes Objekt, das dasIterator-Protokoll implementiert, indem es einenext()-Methode besitzt, die ein Objekt mit zwei Eigenschaften zurückgibt:
valueDer nächste Wert in der Iterationssequenz.
doneDies ist
true, wenn der letzte Wert in der Sequenz bereits verbraucht wurde. Wennvaluezusammen mitdonevorhanden ist, ist es der Rückgabewert des Iterators.
Sobald ein Iterator-Objekt erstellt wurde, kann es explizit iteriert werden, indem wiederholtnext() aufgerufen wird. Das Iterieren über einen Iterator wird als Verbrauch des Iterators bezeichnet, da es im Allgemeinen nur einmal möglich ist. Nachdem ein Endwert geliefert wurde, sollten zusätzliche Aufrufe vonnext() weiterhin{done: true} zurückgeben.
Der häufigste Iterator in JavaScript ist der Array-Iterator, der jeden Wert im zugehörigen Array in Reihenfolge zurückgibt.
Obwohl es leicht vorstellbar ist, dass alle Iteratoren als Arrays ausgedrückt werden könnten, ist dies nicht wahr. Arrays müssen vollständig zugewiesen werden, aber Iteratoren werden nur bei Bedarf verbraucht. Aufgrund dessen können Iteratoren Sequenzen unbegrenzter Größe ausdrücken, wie etwa den Zahlenbereich zwischen0 undInfinity.
Hier ist ein Beispiel, das genau das tun kann. Es ermöglicht die Erstellung eines Bereichsiterators, der eine Sequenz von Ganzzahlen vonstart (einschließlich) bisend (ausschließlich) mit dem Abstandstep definiert. Sein endgültiger Rückgabewert ist die Größe der von ihm erstellten Sequenz, die durch die VariableiterationCount verfolgt wird.
function makeRangeIterator(start = 0, end = Infinity, step = 1) { let nextIndex = start; let iterationCount = 0; const rangeIterator = { next() { let result; if (nextIndex < end) { result = { value: nextIndex, done: false }; nextIndex += step; iterationCount++; return result; } return { value: iterationCount, done: true }; }, }; return rangeIterator;}Die Verwendung des Iterators sieht dann so aus:
const iter = makeRangeIterator(1, 10, 2);let result = iter.next();while (!result.done) { console.log(result.value); // 1 3 5 7 9 result = iter.next();}console.log("Iterated over sequence of size:", result.value); // [5 numbers returned, that took interval in between: 0 to 10]Hinweis:Es ist nicht möglich, reflektiv festzustellen, ob ein bestimmtes Objekt ein Iterator ist. Wenn Sie dies tun müssen, verwenden SieIterables.
Generatofunktion
Obwohl benutzerdefinierte Iteratoren ein nützliches Werkzeug sind, erfordert ihre Erstellung sorgfältige Programmierung, da ihr interner Zustand explizit gepflegt werden muss.Generatofunktionen bieten eine leistungsstarke Alternative: Sie ermöglichen es Ihnen, einen iterativen Algorithmus zu definieren, indem eine einzelne Funktion geschrieben wird, deren Ausführung nicht kontinuierlich ist. Generatofunktionen werden mit derfunction*-Syntax geschrieben.
Bei Aufruf führen Generatofunktionen ihren Code nicht zunächst aus. Stattdessen geben sie einen speziellen Iteratortyp zurück, der alsGenerator bezeichnet wird. Wenn ein Wert durch den Aufruf dernext-Methode des Generators verbraucht wird, führt die Generatofunktion aus, bis sie auf dasyield-Schlüsselwort trifft.
Die Funktion kann so oft wie gewünscht aufgerufen werden und gibt jedes Mal einen neuen Generator zurück. Jeder Generator kann nur einmal durchlaufen werden.
Wir können nun das obige Beispiel anpassen. Das Verhalten dieses Codes ist identisch, aber die Implementierung ist viel einfacher zu schreiben und zu lesen.
function* makeRangeIterator(start = 0, end = Infinity, step = 1) { let iterationCount = 0; for (let i = start; i < end; i += step) { iterationCount++; yield i; } return iterationCount;}Iterables
Ein Objekt istiterierbar, wenn es sein Iterationsverhalten definiert, wie beispielsweise welche Werte in einerfor...of-Konstruktion durchlaufen werden. Einige eingebaute Typen, wieArray oderMap, haben ein Standard-Iterationsverhalten, während andere Typen (wieObject) dies nicht tun.
Umiterierbar zu sein, muss ein Objekt die Methode[Symbol.iterator]() implementieren. Das bedeutet, dass das Objekt (oder eines der Objekte in seinerPrototypkette) eine Eigenschaft mit einemSymbol.iterator-Schlüssel haben muss.
Es kann möglich sein, über ein iterierbares Objekt mehrmals oder nur einmal zu iterieren. Es liegt in der Verantwortung des Programmierers zu wissen, welche Variante der Fall ist.
Iterables, die nur einmal iteriert werden können (wie Generatoren), geben üblicherweisethis von ihrer[Symbol.iterator]()-Methode zurück, während Iterables, die mehrmals durchlaufen werden können, einen neuen Iterator bei jedem Aufruf von[Symbol.iterator]() zurückgeben müssen.
function* makeIterator() { yield 1; yield 2;}const iter = makeIterator();for (const itItem of iter) { console.log(itItem);}console.log(iter[Symbol.iterator]() === iter); // true// This example show us generator(iterator) is iterable object,// which has the [Symbol.iterator]() method return the `iter` (itself),// and consequently, the it object can iterate only _once_.// If we change the [Symbol.iterator]() method of `iter` to a function/generator// which returns a new iterator/generator object, `iter`// can iterate many timesiter[Symbol.iterator] = function* () { yield 2; yield 1;};Benutzerdefinierte Iterables
Sie können Ihre eigenen Iterables so erstellen:
const myIterable = { *[Symbol.iterator]() { yield 1; yield 2; yield 3; },};Benutzerdefinierte Iterables können infor...of-Schleifen oder der Spread-Syntax wie üblich verwendet werden.
for (const value of myIterable) { console.log(value);}// 1// 2// 3[...myIterable]; // [1, 2, 3]Eingebaute Iterables
String,Array,TypedArray,Map undSet sind alle eingebaute Iterables, da ihre Prototypobjekte alle eineSymbol.iterator-Methode haben.
Syntaxen, die Iterables erwarten
Einige Anweisungen und Ausdrücke erwarten Iterables. Zum Beispiel: diefor...of-Schleifen,spread syntax,yield*, unddestructuring-Syntax.
for (const value of ["a", "b", "c"]) { console.log(value);}// "a"// "b"// "c"[..."abc"];// ["a", "b", "c"]function* gen() { yield* ["a", "b", "c"];}gen().next();// { value: "a", done: false }[a, b, c] = new Set(["a", "b", "c"]);a;// "a"Erweiterte Generatoren
Generatoren berechnen ihreyield-Wertebei Bedarf, was es ihnen ermöglicht, Sequenzen effizient darzustellen, deren Berechnung aufwändig ist (oder sogar unendliche Sequenzen, wie oben gezeigt).
Dienext()-Methode akzeptiert auch einen Wert, der verwendet werden kann, um den internen Zustand des Generators zu ändern. Ein Wert, der annext() übergeben wird, wird vonyield empfangen.
Hinweis:Ein Wert, der bei derersten Aufruf vonnext() übergeben wird, wird immer ignoriert.
Hier ist der Fibonacci-Generator, dernext(x) verwendet, um die Sequenz neu zu starten:
function* fibonacci() { let current = 0; let next = 1; while (true) { const reset = yield current; [current, next] = [next, next + current]; if (reset) { current = 0; next = 1; } }}const sequence = fibonacci();console.log(sequence.next().value); // 0console.log(sequence.next().value); // 1console.log(sequence.next().value); // 1console.log(sequence.next().value); // 2console.log(sequence.next().value); // 3console.log(sequence.next().value); // 5console.log(sequence.next().value); // 8console.log(sequence.next(true).value); // 0console.log(sequence.next().value); // 1console.log(sequence.next().value); // 1console.log(sequence.next().value); // 2Sie können einen Generator zwingen, eine Ausnahme auszulösen, indem Sie seinethrow()-Methode aufrufen und den Ausnahme-Wert übergeben, den er auslösen soll. Diese Ausnahme wird aus dem aktuellen angehaltenen Kontext des Generators geworfen, als ob deryield, der derzeit angehalten ist, stattdessen einethrow value-Anweisung wäre.
Wenn die Ausnahme nicht innerhalb des Generators abgefangen wird, wird sie durch den Aufruf vonthrow() nach oben propagiert, und nachfolgende Aufrufe vonnext() führen dazu, dass diedone-Eigenschafttrue ist.
Generatoren haben einereturn()-Methode, um den angegebenen Wert zurückzugeben und den Generator selbst zu beenden.