Dieser Inhalt wurde automatisch aus dem Englischen übersetzt, und kann Fehler enthalten.Erfahre mehr über dieses Experiment.
Iterationsprotokolle
Iterationsprotokolle sind keine neuen eingebauten Funktionen oder Syntaxelemente, sondernProtokolle. Diese Protokolle können von jedem Objekt implementiert werden, indem einige Konventionen befolgt werden.
Es gibt zwei Protokolle: Dasiterierbare Protokoll und dasIterator-Protokoll.
In diesem Artikel
Das iterierbare Protokoll
Das iterierbare Protokoll ermöglicht es JavaScript-Objekten, ihr Iterationsverhalten zu definieren oder anzupassen, z. B. welche Werte in einemfor...of-Konstrukt durchlaufen werden. Einige eingebaute Typen sindeingebaute Iterables mit einem standardmäßigen Iterationsverhalten, wieArray oderMap, während andere Typen (wieObject) nicht iterierbar sind.
Umiterierbar zu sein, muss ein Objekt die Methode[Symbol.iterator]() implementieren, was bedeutet, dass das Objekt (oder eines der Objekte in seinerPrototypenkette) eine Eigenschaft mit einem[Symbol.iterator]-Schlüssel haben muss, der über die KonstanteSymbol.iterator verfügbar ist:
[Symbol.iterator]()Eine Funktion ohne Argumente, die ein Objekt zurückgibt, das demIterator-Protokoll entspricht.
Wann immer ein Objekt iteriert werden muss (z. B. zu Beginn einerfor...of-Schleife), wird seine[Symbol.iterator]()-Methode ohne Argumente aufgerufen, und der zurückgegebeneIterator wird verwendet, um die zu iterierenden Werte zu erhalten.
Beachten Sie, dass diese Funktion ohne Argumente als Methode für das iterierbare Objekt aufgerufen wird. Daher kann das Schlüsselwortthis innerhalb der Funktion verwendet werden, um auf die Eigenschaften des iterierbaren Objekts zuzugreifen und zu bestimmen, was während der Iteration bereitgestellt werden soll.
Diese Funktion kann eine gewöhnliche Funktion sein oder sie kann eine Generatorfunktion sein, sodass bei ihrer Ausführung ein Iteratorobjekt zurückgegeben wird. Innerhalb dieser Generatorfunktion kann jeder Eintrag durch die Nutzung vonyield bereitgestellt werden.
Das Iterator-Protokoll
Das Iterator-Protokoll definiert eine standardisierte Methode zur Erzeugung einer Folge von Werten (endlicher oder unendlicher), und potenziell einen Rückgabewert, wenn alle Werte generiert wurden.
Ein Objekt ist ein Iterator, wenn es einenext() Methode mit den folgenden Semantiken implementiert:
next()Eine Funktion, die null oder ein Argument akzeptiert und ein Objekt zurückgibt, das der
IteratorResult-Schnittstelle entspricht (siehe unten). Wenn ein nicht-objekthafter Wert (wiefalseoderundefined) zurückgegeben wird, während eine eingebaute Sprachfunktion (wiefor...of) den Iterator verwendet, wird einTypeError("iterator.next() returned a non-object value") ausgelöst.
Alle Methoden des Iterator-Protokolls (next(),return(), undthrow()) sollten ein Objekt zurückgeben, das dieIteratorResult-Schnittstelle implementiert. Es muss über die folgenden Eigenschaften verfügen:
doneOptionalEin Boolean, der
falseist, wenn der Iterator in der Lage war, den nächsten Wert in der Folge zu erzeugen. (Dies entspricht dem Nicht-Spezifizieren derdone-Eigenschaft.)Hat den Wert
true, wenn der Iterator seine Folge abgeschlossen hat. In diesem Fall gibtvalueoptional den Rückgabewert des Iterators an.valueOptionalEin beliebiger JavaScript-Wert, der vom Iterator zurückgegeben wird. Kann weggelassen werden, wenn
donetrueist.
In der Praxis sind weder die eine noch die andere Eigenschaft streng erforderlich; wenn ein Objekt ohne eine der beiden Eigenschaften zurückgegeben wird, ist es effektiv gleichwertig zu{ done: false, value: undefined }.
Wenn ein Iterator ein Ergebnis mitdone: true zurückgibt, sollten alle nachfolgenden Aufrufe vonnext() erwartet werden,done: true zurückzugeben, obwohl dies auf Sprachebene nicht erzwungen wird.
Dienext-Methode kann einen Wert empfangen, der dem Methodenkörper zur Verfügung gestellt wird. Kein eingebautes Sprachmerkmal wird irgendeinen Wert übergeben. Der Wert, der an dienext-Methode vonGeneratoren übergeben wird, wird zum Wert des entsprechendenyield-Ausdrucks.
Optional kann der Iterator auch diereturn(value) undthrow(exception) Methoden implementieren, die, wenn sie aufgerufen werden, dem Iterator mitteilen, dass der Aufrufer mit dem Iterieren fertig ist und alle notwendigen Aufräumarbeiten (wie das Schließen von Datenbankverbindungen) durchführen kann.
return(value)OptionalEine Funktion, die null oder ein Argument akzeptiert und ein Objekt zurückgibt, das der
IteratorResult-Schnittstelle entspricht, typischerweise mitvaluegleich dem übergebenenvalueunddonegleichtrue. Das Aufrufen dieser Methode teilt dem Iterator mit, dass der Aufrufer nicht die Absicht hat, weiterenext()-Aufrufe zu tätigen, und alle Aufräumaktionen durchführen kann. Wenn eingebaute Sprachmerkmalereturn()für Aufräumarbeiten aufrufen, istvalueimmerundefined.throw(exception)OptionalEine Funktion, die null oder ein Argument akzeptiert und ein Objekt zurückgibt, das der
IteratorResult-Schnittstelle entspricht, typischerweise mitdonegleichtrue. Das Aufrufen dieser Methode teilt dem Iterator mit, dass der Aufrufer einen Fehlerzustand erkennt, undexceptionist typischerweise eineError-Instanz. Kein eingebautes Sprachmerkmal ruftthrow()für Aufräumzwecke auf - es ist ein spezielles Merkmal von Generatoren für die Symmetrie vonreturn/throw.
Hinweis:Es ist nicht möglich, reflektiv (d.h. ohne tatsächlichnext() aufzurufen und das zurückgegebene Ergebnis zu validieren) zu wissen, ob ein bestimmtes Objekt das Iterator-Protokoll implementiert.
Es ist sehr einfach, einen Iterator auch iterierbar zu machen: Einfach eine[Symbol.iterator]()-Methode implementieren, diethis zurückgibt.
// Satisfies both the Iterator Protocol and Iterableconst myIterator = { next() { // … }, [Symbol.iterator]() { return this; },};Ein solches Objekt wird alsiterierbarer Iterator bezeichnet. Dies ermöglicht es einem Iterator, von den verschiedenen Syntaxen konsumiert zu werden, die Iterables erwarten – daher ist es selten nützlich, das Iterator-Protokoll zu implementieren, ohne auch Iterables zu implementieren. (Tatsächlich erwarten fast alle Syntaxen und APIsIterables, nichtIterators.) DasGeneratorobjekt ist ein Beispiel:
const generatorObject = (function* () { yield 1; yield 2; yield 3;})();console.log(typeof generatorObject.next);// "function" — it has a next method (which returns the right result), so it's an iteratorconsole.log(typeof generatorObject[Symbol.iterator]);// "function" — it has a [Symbol.iterator] method (which returns the right iterator), so it's an iterableconsole.log(generatorObject[Symbol.iterator]() === generatorObject);// true — its [Symbol.iterator] method returns itself (an iterator), so it's an iterable iteratorAlle eingebauten Iteratoren erben vonIterator.prototype, das die[Symbol.iterator]()-Methode implementiert, diethis zurückgibt, so dass eingebaute Iteratoren ebenfalls iterierbar sind.
Es ist jedoch, wann immer möglich, besser, dassiterable[Symbol.iterator]() verschiedene Iteratoren zurückgibt, die immer von Anfang an beginnen, wie esSet.prototype[Symbol.iterator]() tut.
Die asynchronen Iterator- und Iterable-Protokolle
Es gibt ein weiteres Paar von Protokollen für die asynchrone Iteration, die alsasynchrone Iterator- undasynchrone Iterable-Protokolle bekannt sind. Sie haben sehr ähnliche Schnittstellen im Vergleich zu den iterierbaren und Iterator-Protokollen, mit der Ausnahme, dass jeder Rückgabewert von den Aufrufen der Iterator-Methoden in ein Promise eingeschlossen wird.
Ein Objekt implementiert das asynchrone Iterable-Protokoll, wenn es die folgenden Methoden implementiert:
[Symbol.asyncIterator]()Eine Funktion ohne Argumente, die ein Objekt zurückgibt, das dem asynchronen Iterator-Protokoll entspricht.
Ein Objekt implementiert das asynchrone Iterator-Protokoll, wenn es die folgenden Methoden implementiert:
next()Eine Funktion, die null oder ein Argument akzeptiert und ein Promise zurückgibt. Das Promise erfüllt ein Objekt, das der
IteratorResult-Schnittstelle entspricht, und die Eigenschaften haben die gleichen Semantiken wie die des synchronen Iterators.return(value)OptionalEine Funktion, die null oder ein Argument akzeptiert und ein Promise zurückgibt. Das Promise erfüllt ein Objekt, das der
IteratorResult-Schnittstelle entspricht, und die Eigenschaften haben die gleichen Semantiken wie die des synchronen Iterators.throw(exception)OptionalEine Funktion, die null oder ein Argument akzeptiert und ein Promise zurückgibt. Das Promise erfüllt ein Objekt, das der
IteratorResult-Schnittstelle entspricht, und die Eigenschaften haben die gleichen Semantiken wie die des synchronen Iterators.
Interaktionen zwischen der Sprache und den Iterationsprotokollen
Die Sprache spezifiziert APIs, die entweder Iterables oder Iteratoren erzeugen oder konsumieren.
Eingebaute Iterables
String,Array,TypedArray,Map,Set, undSegments (zurückgegeben vonIntl.Segmenter.prototype.segment()) sind alle eingebaute Iterables, da jedes ihrerprototype-Objekte eine[Symbol.iterator]()-Methode implementiert. Zusätzlich sind dasarguments-Objekt und einige DOM-Sammlungstypen wieNodeList auch iterierbar. Es gibt kein Objekt in der Kern-JavaScript-Sprache, das asynchron iterierbar ist. Einige Web-APIs, wieReadableStream, haben dieSymbol.asyncIterator-Methode standardmäßig gesetzt.
Generatorfunktionen gebenGeneratorobjekte zurück, die iterierbare Iteratoren sind.Asynchrone Generatorfunktionen gebenasynchrone Generatorobjekte zurück, die asynchrone iterierbare Iteratoren sind.
Die von eingebauten Iterables zurückgegebenen Iteratoren erben alle von einer gemeinsamen KlasseIterator, die die erwähnte[Symbol.iterator]() { return this; }-Methode implementiert, wodurch sie alle iterierbare Iteratoren sind. DieIterator-Klasse bietet zusätzlich zu dernext()-Methode, die für das Iterator-Protokoll erforderlich ist, weitereHilfsmethoden. Sie können die Prototypenkette eines Iterators überprüfen, indem Sie ihn in einer grafischen Konsole protokollieren.
console.log([][Symbol.iterator]());Array Iterator {} [[Prototype]]: Array Iterator ==> This is the prototype shared by all array iterators next: ƒ next() Symbol(Symbol.toStringTag): "Array Iterator" [[Prototype]]: Object ==> This is the prototype shared by all built-in iterators Symbol(Symbol.iterator): ƒ [Symbol.iterator]() [[Prototype]]: Object ==> This is Object.prototypeEingebaute APIs, die Iterables akzeptieren
Es gibt viele APIs, die Iterables akzeptieren. Einige Beispiele sind:
Map()WeakMap()Set()WeakSet()Promise.all()Promise.allSettled()Promise.race()Promise.any()Array.from()Object.groupBy()Map.groupBy()
const myObj = {};new WeakSet( (function* () { yield {}; yield myObj; yield {}; })(),).has(myObj); // trueSyntaxen, die Iterables erwarten
Einige Anweisungen und Ausdrücke erwarten Iterables, wie beispielsweise diefor...of Schleifen,Array- und Parameterverbreitung,yield*, undArray-Destrukturierung:
for (const value of ["a", "b", "c"]) { console.log(value);}// "a"// "b"// "c"console.log([..."abc"]); // ["a", "b", "c"]function* gen() { yield* ["a", "b", "c"];}console.log(gen().next()); // { value: "a", done: false }[a, b, c] = new Set(["a", "b", "c"]);console.log(a); // "a"Wenn eingebaute Syntaxen einen Iterator iterieren und das letzte Ergebnisdone istfalse (d.h. der Iterator kann weitere Werte erzeugen), aber keine weiteren Werte benötigt werden, wird diereturn-Methode aufgerufen, falls vorhanden. Dies kann zum Beispiel passieren, wenn einbreak oderreturn in einerfor...of-Schleife auftritt oder wenn alle Bezeichner bereits in einer Array-Destrukturierung gebunden sind.
const obj = { [Symbol.iterator]() { let i = 0; return { next() { i++; console.log("Returning", i); if (i === 3) return { done: true, value: i }; return { done: false, value: i }; }, return() { console.log("Closing"); return { done: true }; }, }; },};const [a] = obj;// Returning 1// Closingconst [b, c, d] = obj;// Returning 1// Returning 2// Returning 3// Already reached the end (the last call returned `done: true`),// so `return` is not calledconsole.log([b, c, d]); // [1, 2, undefined]; the value associated with `done: true` is not reachablefor (const b of obj) { break;}// Returning 1// ClosingDiefor await...of Schleife undyield* inasynchronen Generatorfunktionen (aber nichtsynchronen Generatorfunktionen) sind die einzigen Möglichkeiten, mit asynchronen Iterables zu interagieren. Die Nutzung vonfor...of, Array-Spreading, usw. auf einem asynchronen Iterable, das nicht auch ein synchrones Iterable ist (d.h. es hat[Symbol.asyncIterator]() aber kein[Symbol.iterator]()) wird einen TypeError verursachen: x ist nicht iterierbar.
Fehlerbehandlung
Da die Iteration einen Kontrolltransfer zwischen dem Iterator und dem Verbraucher beinhaltet, erfolgt die Fehlerbehandlung in beide Richtungen: wie der Verbraucher Fehler behandelt, die vom Iterator ausgelöst werden, und wie der Iterator Fehler behandelt, die vom Verbraucher ausgelöst werden. Wenn Sie eine der eingebauten Methoden zur Iteration verwenden, kann die Sprache auch Fehler auslösen, weil das Iterable bestimmteInvarianten verletzt. Wir werden beschreiben, wie eingebaute Syntaxen Fehler erzeugen und behandeln, die als Leitfaden für Ihren eigenen Code dienen können, wenn Sie den Iterator manuell steuern.
Nicht gut geformte Iterables
Fehler können auftreten, wenn der Iterator aus dem Iterable erworben wird. Die hier durchgesetzte Sprachinvariante ist, dass das Iterable einen gültigen Iterator produzieren muss:
- Es hat eine aufrufbare
[Symbol.iterator]()-Methode. - Die
[Symbol.iterator]()-Methode gibt ein Objekt zurück. - Das von
[Symbol.iterator]()zurückgegebene Objekt hat eine aufrufbarenext()-Methode.
Bei der Verwendung eingebauter Syntaxen, um die Iteration auf einem nicht gut geformten Iterable zu initiieren, wird ein TypeError ausgelöst.
const nonWellFormedIterable = { [Symbol.iterator]: 1 };[...nonWellFormedIterable]; // TypeError: nonWellFormedIterable is not iterablenonWellFormedIterable[Symbol.iterator] = () => 1;[...nonWellFormedIterable]; // TypeError: [Symbol.iterator]() returned a non-object valuenonWellFormedIterable[Symbol.iterator] = () => ({});[...nonWellFormedIterable]; // TypeError: nonWellFormedIterable[Symbol.iterator]().next is not a functionFür asynchrone Iterables, wenn seine[Symbol.asyncIterator]()-Eigenschaft den Wertundefined odernull hat, fällt JavaScript darauf zurück, die[Symbol.iterator]-Eigenschaft zu verwenden (und den resultierenden Iterator in einen asynchronen Iterator zu verwandel, indem die Methodenweitergeleitet werden). Andernfalls muss die[Symbol.asyncIterator]-Eigenschaft auch den oben genannten Invarianten entsprechen.
Diese Fehlerart kann verhindert werden, indem vor dem Versuch, es zu iterieren, das Iterable validiert wird. Es ist jedoch ziemlich selten, weil Sie normalerweise den Typ des Objekts kennen, über das Sie iterieren. Wenn Sie dieses Iterable von einem anderen Code erhalten, sollten Sie den Fehler einfach an den Aufrufer weiterleiten, damit er weiß, dass ein ungültiger Eingabewert bereitgestellt wurde.
Fehler während der Iteration
Die meisten Fehler treten auf, wenn der Iterator weitergeschaltet wird (durch Aufrufen vonnext()). Die hier durchgesetzte Sprachinvariante ist, dass dienext()-Methode ein Objekt (für asynchrone Iteratoren, ein Objekt nach dem Warten) zurückgeben muss. Andernfalls wird ein TypeError ausgelöst.
Wenn die Invariante verletzt oder dienext()-Methode einen Fehler auslöst (für asynchrone Iteratoren kann sie auch ein abgelehntes Promise zurückgeben), wird der Fehler an den Aufrufer weitergeleitet. Bei eingebauten Syntaxen wird die in Arbeit befindliche Iteration ohne erneuten Versuch oder Aufräumarbeiten abgebrochen (mit der Annahme, dass, wenn dienext()-Methode den Fehler ausgelöst hat, dann hat sie bereits aufgeräumt). Wenn Sienext() manuell aufrufen, können Sie den Fehler auffangen und dienext()-Methode erneut aufrufen, aber im Allgemeinen sollten Sie davon ausgehen, dass der Iterator bereits geschlossen ist.
Wenn der Aufrufer aus einem anderen Grund als den Fehlern in den vorangegangenen Abschnitten beschließt, die Iteration zu beenden, wie z. B. wenn er in seinem eigenen Code in einen Fehlerzustand gerät (z. B., während er einen ungültigen Wert behandelt, der vom Iterator erzeugt wurde), sollte er diereturn()-Methode des Iterators aufrufen, falls diese vorhanden ist. Dadurch kann der Iterator alle Aufräumarbeiten durchführen. Diereturn()-Methode wird nur für vorzeitiges Beenden aufgerufen – wennnext()done: true zurückgibt, wird diereturn()-Methode nicht aufgerufen, mit der Annahme, dass der Iterator bereits aufgeräumt hat.
Diereturn()-Methode könnte ebenfalls ungültig sein! Die Sprache erzwingt auch, dass diereturn()-Methode ein Objekt zurückgibt, andernfalls wird ein TypeError ausgelöst. Wenn diereturn()-Methode einen Fehler auslöst, wird der Fehler an den Aufrufer weitergeleitet. Wenn jedoch diereturn()-Methode aufgerufen wird, weil der Aufrufer in seinem eigenen Code einen Fehler entdeckt hat, dann überschreibt dieser Fehler den Fehler, der von derreturn()-Methode ausgelöst wird.
Normalerweise implementiert der Aufrufer die Fehlerbehandlung wie folgt:
try { for (const value of iterable) { // … }} catch (e) { // Handle the error}Dercatch kann Fehler auffangen, die auftreten, wenniterable kein gültiges Iterable ist, wennnext() einen Fehler auslöst, wennreturn() einen Fehler auslöst (wenn diefor-Schleife frühzeitig beendet wird), und wenn der Körper derfor-Schleife einen Fehler wirft.
Die meisten Iteratoren werden mit Generatorfunktionen implementiert, daher demonstrieren wir, wie Generatorfunktionen typischerweise Fehler behandeln:
function* gen() { try { yield doSomething(); yield doSomethingElse(); } finally { cleanup(); }}Der Mangel an einemcatch hier führt dazu, dass Fehler, die vondoSomething() oderdoSomethingElse() geworfen werden, an den Aufrufer vongen weitergegeben werden. Wenn diese Fehler innerhalb der Generatorfunktion (was gleichermaßen ratsam ist) aufgefangen werden, kann die Generatorfunktion entscheiden, ob weitere Werte bereitgestellt oder die Ausführung frühzeitig beendet werden soll. Derfinally-Block ist jedoch für Generatoren, die offene Ressourcen halten, notwendig. Derfinally-Block wird garantiert ausgeführt, entweder wenn das letztenext() aufgerufen wird oder wennreturn() aufgerufen wird.
Weiterleitung von Fehlern
Einige eingebaute Syntaxen umwickeln einen Iterator in einen anderen Iterator. Dazu gehören der vonIterator.from() erzeugte Iterator,Iterator-Hilfsmethoden (map(),filter(),take(),drop(), undflatMap()),yield*, und ein versteckter Wrapper, wenn Sie asynchrone Iteration (for await...of,Array.fromAsync) auf synchronen Iteratoren verwenden. Der umschlossene Iterator ist dann dafür verantwortlich, Fehler zwischen dem inneren Iterator und dem Aufrufer weiterzuleiten.
- Alle Wrapper-Iterators leiten die
next()-Methode des inneren Iterators direkt weiter, einschließlich seines Rückgabewerts und der ausgelösten Fehler. - Wrapper-Iterators leiten im Allgemeinen die
return()-Methode des inneren Iterators direkt weiter. Wenn diereturn()-Methode beim inneren Iterator nicht existiert, wird stattdessen{ done: true, value: undefined }zurückgegeben. Im Fall von Iterator-Hilfsmethoden: Wenn dienext()-Methode des Iterator-Helfers nicht aufgerufen wurde, gibt der aktuelle Iterator, nachdem versucht wurde,return()auf dem inneren Iterator aufzurufen, immer{ done: true, value: undefined }zurück. Dies ist konsistent mit Generatorfunktionen, bei denen die Ausführung noch nicht in denyield*-Ausdruck eingetreten ist. yield*ist die einzige eingebaute Syntax, die diethrow()-Methode des inneren Iterators weiterleitet. Informationen darüber, wieyield*diereturn()- undthrow()-Methoden weiterleitet, finden Sie in der entsprechenden Dokumentation.
Beispiele
>Benutzerdefinierte Iterables
Sie können Ihre eigenen Iterables so erstellen:
const myIterable = { *[Symbol.iterator]() { yield 1; yield 2; yield 3; },};console.log([...myIterable]); // [1, 2, 3]Einfacher Iterator
Iteratoren sind von Natur aus zustandsbehaftet. Wenn Sie ihn nicht alsGeneratorfunktion (wie im obigen Beispiel gezeigt) definieren, sollten Sie den Zustand in einem Abschluss einkapseln.
function makeIterator(array) { let nextIndex = 0; return { next() { return nextIndex < array.length ? { value: array[nextIndex++], done: false, } : { done: true, }; }, };}const it = makeIterator(["yo", "ya"]);console.log(it.next().value); // 'yo'console.log(it.next().value); // 'ya'console.log(it.next().done); // trueUnendlicher Iterator
function idMaker() { let index = 0; return { next() { return { value: index++, done: false, }; }, };}const it = idMaker();console.log(it.next().value); // 0console.log(it.next().value); // 1console.log(it.next().value); // 2// …Definieren eines Iterables mit einem Generator
function* makeGenerator(array) { let nextIndex = 0; while (nextIndex < array.length) { yield array[nextIndex++]; }}const gen = makeGenerator(["yo", "ya"]);console.log(gen.next().value); // 'yo'console.log(gen.next().value); // 'ya'console.log(gen.next().done); // truefunction* idMaker() { let index = 0; while (true) { yield index++; }}const it = idMaker();console.log(it.next().value); // 0console.log(it.next().value); // 1console.log(it.next().value); // 2// …Definieren eines Iterables mit einer Klasse
Die Zustandseinkapsulierung kann auch mitprivaten Feldern durchgeführt werden.
class SimpleClass { #data; constructor(data) { this.#data = data; } [Symbol.iterator]() { // Use a new index for each iterator. This makes multiple // iterations over the iterable safe for non-trivial cases, // such as use of break or nested looping over the same iterable. let index = 0; return { // Note: using an arrow function allows `this` to point to the // one of `[Symbol.iterator]()` instead of `next()` next: () => { if (index >= this.#data.length) { return { done: true }; } return { value: this.#data[index++], done: false }; }, }; }}const simple = new SimpleClass([1, 2, 3, 4, 5]);for (const val of simple) { console.log(val); // 1 2 3 4 5}Überschreiben von eingebauten Iterables
Ein Beispiel: EinString ist ein eingebautes iterierbares Objekt:
const someString = "hi";console.log(typeof someString[Symbol.iterator]); // "function"DerStandarditerator einesString gibt die Codepunkte des Strings nacheinander zurück:
const iterator = someString[Symbol.iterator]();console.log(`${iterator}`); // "[object String Iterator]"console.log(iterator.next()); // { value: "h", done: false }console.log(iterator.next()); // { value: "i", done: false }console.log(iterator.next()); // { value: undefined, done: true }Sie können das Iterationsverhalten neu definieren, indem Sie Ihren eigenen[Symbol.iterator]() bereitstellen:
// need to construct a String object explicitly to avoid auto-boxingconst someString = new String("hi");someString[Symbol.iterator] = function () { return { // this is the iterator object, returning a single element (the string "bye") next() { return this._first ? { value: "bye", done: (this._first = false) } : { done: true }; }, _first: true, };};Beachten Sie, wie die Neudefinition von[Symbol.iterator]() das Verhalten von eingebauten Konstrukten beeinflusst, die das Iterationsprotokoll verwenden:
console.log([...someString]); // ["bye"]console.log(`${someString}`); // "hi"Gleichzeitige Änderungen beim Iterieren
Fast alle Iterables haben die gleiche grundlegende Semantik: Sie kopieren die Daten nicht zu dem Zeitpunkt, an dem die Iteration beginnt. Stattdessen behalten sie einen Zeiger und bewegen ihn. Daher kann es passieren, dass, wenn Sie Elemente in der Sammlung während des Iterierens hinzufügen, löschen oder ändern, Sie versehentlich ändern, ob andereunveränderte Elemente in der Sammlung besucht werden. Dies ist sehr ähnlich wieiterative Array-Methoden funktionieren.
Betrachten Sie das folgende Beispiel mit einemURLSearchParams:
const searchParams = new URLSearchParams( "deleteme1=value1&key2=value2&key3=value3",);// Delete unwanted keysfor (const [key, value] of searchParams) { console.log(key); if (key.startsWith("deleteme")) { searchParams.delete(key); }}// Output:// deleteme1// key3Beachten Sie, dass niekey2 geloggt wird. Dies liegt daran, dass einURLSearchParams unterliegend eine Liste von Schlüssel-Wert-Paaren ist. Wenndeleteme1 besucht und gelöscht wird, werden alle anderen Einträge um eins nach links verschoben, sodasskey2 die Position einnimmt, diedeleteme1 früher hatte, und wenn der Zeiger zum nächsten Schlüssel geht, landet er aufkey3.
Bestimmte Implementierungen von Iterables vermeiden dieses Problem durch Setzen von "Grabstein"-Werten, um ein Verschieben der verbleibenden Werte zu vermeiden. Betrachten Sie den ähnlichen Code mit einemMap:
const myMap = new Map([ ["deleteme1", "value1"], ["key2", "value2"], ["key3", "value3"],]);for (const [key, value] of myMap) { console.log(key); if (key.startsWith("deleteme")) { myMap.delete(key); }}// Output:// deleteme1// key2// key3Beachten Sie, dass alle Schlüssel geloggt werden. Dies liegt daran, dassMap die verbleibenden Schlüssel nicht verschiebt, wenn einer gelöscht wird. Wenn Sie etwas Ähnliches implementieren möchten, sieht es möglicherweise so aus:
const tombstone = Symbol("tombstone");class MyIterable { #data; constructor(data) { this.#data = data; } delete(deletedKey) { for (let i = 0; i < this.#data.length; i++) { if (this.#data[i][0] === deletedKey) { this.#data[i] = tombstone; return true; } } return false; } *[Symbol.iterator]() { for (const data of this.#data) { if (data !== tombstone) { yield data; } } }}const myIterable = new MyIterable([ ["deleteme1", "value1"], ["key2", "value2"], ["key3", "value3"],]);for (const [key, value] of myIterable) { console.log(key); if (key.startsWith("deleteme")) { myIterable.delete(key); }}Warnung:Gleichzeitige Änderungen sind im Allgemeinen sehr fehleranfällig und verwirrend. Sofern Sie nicht genau wissen, wie das Iterable implementiert ist, sollten Sie vermeiden, die Sammlung während des Iterierens zu ändern.
Spezifikationen
| Specification |
|---|
| ECMAScript® 2026 Language Specification> # sec-iteration> |