Dieser Inhalt wurde automatisch aus dem Englischen übersetzt, und kann Fehler enthalten. Erfahre mehr über dieses Experiment.

View in English Always switch to English

Iterationsprotokolle

Iterationsprotokolle sind keine neuen eingebauten Funktionen oder Syntaxe, sondern Protokolle. Diese Protokolle können von jedem Objekt implementiert werden, indem bestimmte Konventionen befolgt werden.

Es gibt zwei Protokolle: Das iterable Protokoll und das iterator Protokoll.

Das iterable Protokoll

Das iterable Protokoll ermöglicht es JavaScript-Objekten, ihr Iterationsverhalten zu definieren oder anzupassen, wie zum Beispiel welche Werte in einer for...of-Struktur durchlaufen werden. Einige eingebaute Typen sind eingebaute Iterables mit einem Standard-Iterationsverhalten, wie Array oder Map, während andere Typen (wie Object) dies nicht sind.

Um iterierbar zu sein, muss ein Objekt die Methode [Symbol.iterator]() implementieren. Das bedeutet, das Objekt (oder eines der Objekte in seiner Prototypenkette) muss eine Eigenschaft mit einem [Symbol.iterator] Schlüssel haben, die durch die Konstante Symbol.iterator verfügbar ist:

[Symbol.iterator]()

Eine Funktion ohne Argumente, die ein Objekt zurückgibt, das dem iterator Protokoll entspricht.

Wann immer ein Objekt iteriert werden muss (wie zu Beginn einer for...of Schleife), wird seine [Symbol.iterator]()-Methode ohne Argumente aufgerufen, und der zurückgegebene Iterator wird verwendet, um die zu iterierenden Werte zu erhalten.

Beachten Sie, dass, wenn diese Funktion ohne Argumente aufgerufen wird, sie als Methode am iterierbaren Objekt aufgerufen wird. Daher kann innerhalb der Funktion das this-Schlüsselwort verwendet werden, um auf die Eigenschaften des iterierbaren Objekts zuzugreifen und zu entscheiden, was während der Iteration bereitgestellt wird.

Diese Funktion kann eine normale Funktion sein oder eine Generatorfunktion, so dass beim Aufruf ein Iteratorobjekt zurückgegeben wird. Innerhalb dieser Generatorfunktion kann jeder Eintrag durch Verwendung von yield bereitgestellt werden.

Das iterator Protokoll

Das iterator Protokoll definiert eine standardisierte Methode, um eine Sequenz von Werten (entweder endlich oder unendlich) zu erzeugen und möglicherweise einen Rückgabewert, wenn alle Werte erzeugt wurden.

Ein Objekt ist ein Iterator, wenn es eine next() Methode mit 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-Objekt-Wert zurückgegeben wird (wie false oder undefined), während eine eingebaute Sprachfunktion (wie for...of) den Iterator verwendet, wird ein TypeError ("iterator.next() returned a non-object value") ausgelöst.

Alle Iterator-Protokollmethoden (next(), return(), und throw()) sollten ein Objekt zurückgeben, das die IteratorResult Schnittstelle implementiert. Es muss die folgenden Eigenschaften haben:

done Optional

Ein Boolean, der false ist, wenn der Iterator das nächste Element in der Sequenz erzeugen konnte. (Dies entspricht dem Fehlen der done-Eigenschaft.)

Hat den Wert true, wenn der Iterator seine Sequenz abgeschlossen hat. In diesem Fall gibt value optional den Rückgabewert des Iterators an.

value Optional

Jeder JavaScript-Wert, der vom Iterator zurückgegeben wird. Kann weggelassen werden, wenn done true ist.

In der Praxis ist keine der beiden Eigenschaften unbedingt erforderlich; wenn ein Objekt ohne eine der beiden Eigenschaften zurückgegeben wird, ist es effektiv gleichwertig mit { done: false, value: undefined }.

Wenn ein Iterator ein Ergebnis mit done: true zurückgibt, sollten alle nachfolgenden Aufrufe an next() ebenfalls done: true zurückgeben, obwohl dies auf der Sprachebene nicht erzwungen wird.

Die next-Methode kann einen Wert empfangen, der im Methodenrumpf verfügbar gemacht wird. Keine eingebaute Sprachfunktion wird einen Wert übergeben. Der Wert, der an die next-Methode von Generatoren übergeben wird, wird zum Wert des entsprechenden yield-Ausdrucks.

Optional kann der Iterator auch die Methoden return(value) und throw(exception) implementieren, die, wenn sie aufgerufen werden, dem Iterator mitteilen, dass der Aufrufer mit dem Iterieren fertig ist und alle notwendigen Bereinigungen durchführen kann (wie das Schließen von Datenbankverbindungen).

return(value) Optional

Eine Funktion, die null oder ein Argument akzeptiert und ein Objekt zurückgibt, das der IteratorResult Schnittstelle entspricht, typischerweise mit value, das dem übergebenen value entspricht und done, das true ist. Dieser Methodeaufruf teilt dem Iterator mit, dass der Aufrufer nicht beabsichtigt, weitere next()-Aufrufe zu machen und alle Bereinigungsaktionen durchgeführt werden können. Wenn eingebaute Sprachfunktionen return() für die Bereinigung aufrufen, ist value immer undefined.

throw(exception) Optional

Eine Funktion, die null oder ein Argument akzeptiert und ein Objekt zurückgibt, das der IteratorResult Schnittstelle entspricht, typischerweise mit done, das true ist. Dieser Methodeaufruf teilt dem Iterator mit, dass der Aufrufer eine Fehlerbedingung erkennt und exception typischerweise eine Error-Instanz ist. Keine eingebaute Sprachfunktion ruft throw() zu Bereinigungszwecken auf - es ist eine Spezialfunktion von Generatoren für die Symmetrie von return/throw.

Hinweis: Es ist nicht möglich, reflektiv (d.h. ohne tatsächlich next() aufzurufen und das zurückgegebene Ergebnis zu validieren) zu bestimmen, ob ein bestimmtes Objekt das Iterator-Protokoll implementiert.

Es ist sehr einfach, einen Iterator auch iterierbar zu machen: implementieren Sie einfach eine [Symbol.iterator]() Methode, die this zurückgibt.

js
// Satisfies both the Iterator Protocol and Iterable
const myIterator = {
  next() {
    // …
  },
  [Symbol.iterator]() {
    return this;
  },
};

Ein solches Objekt wird als iterierbarer Iterator bezeichnet. Dies ermöglicht es einem Iterator, von den verschiedenen Syntaxen verwendet zu werden, die Iterables erwarten - daher ist es selten nützlich, das Iterator-Protokoll zu implementieren, ohne auch Iterable zu implementieren. (Tatsächlich erwarten fast alle Syntaxen und APIs Iterables, keine Iterators.) Das Generatorobjekt ist ein Beispiel:

js
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 iterator

console.log(typeof generatorObject[Symbol.iterator]);
// "function" — it has a [Symbol.iterator] method (which returns the right iterator), so it's an iterable

console.log(generatorObject[Symbol.iterator]() === generatorObject);
// true — its [Symbol.iterator] method returns itself (an iterator), so it's an iterable iterator

Alle eingebauten Iteratoren erben von Iterator.prototype, das die [Symbol.iterator]() Methode als Rückgabe von this implementiert, so dass eingebaute Iteratoren auch iterierbar sind.

Wenn möglich, ist es jedoch besser, dass iterable[Symbol.iterator]() verschiedene Iteratoren zurückgibt, die immer am Anfang beginnen, wie Set.prototype[Symbol.iterator]().

Die async iterator und async iterable Protokolle

Es gibt ein weiteres Paar von Protokollen für die asynchrone Iteration, genannt async iterator und async iterable Protokolle. Sie haben sehr ähnliche Schnittstellen im Vergleich zu den iterable und iterator Protokollen, außer dass jeder Rückgabewert von den Aufrufen der Iteratormethoden in ein Promise eingehüllt ist.

Ein Objekt implementiert das async iterable Protokoll, wenn es die folgenden Methoden implementiert:

[Symbol.asyncIterator]()

Eine Funktion ohne Argumente, die ein Objekt zurückgibt, das dem async iterator Protokoll entspricht.

Ein Objekt implementiert das async 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 sich zu einem Objekt, das der IteratorResult Schnittstelle entspricht, und die Eigenschaften haben die gleichen Semantiken wie die des synchronen Iterators.

return(value) Optional

Eine Funktion, die null oder ein Argument akzeptiert und ein Promise zurückgibt. Das Promise erfüllt sich zu einem Objekt, das der IteratorResult Schnittstelle entspricht, und die Eigenschaften haben die gleichen Semantiken wie die des synchronen Iterators.

throw(exception) Optional

Eine Funktion, die null oder ein Argument akzeptiert und ein Promise zurückgibt. Das Promise erfüllt sich zu einem 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 erzeugen oder konsumieren.

Eingebaute Iterables

String, Array, TypedArray, Map, Set, und Segments (zurückgegeben von Intl.Segmenter.prototype.segment()) sind allesamt eingebaute Iterables, da jedes ihrer prototype Objekte eine [Symbol.iterator]() Methode implementiert. Zudem sind das arguments Objekt und einige DOM-Sammlungstypen wie NodeList ebenfalls iterierbar. Es gibt kein Objekt in der Kern-JavaScript-Sprache, das async iterable ist. Einige Web APIs, wie ReadableStream, haben die Symbol.asyncIterator Methode standardmäßig gesetzt.

Generatorfunktionen geben Generatorobjekte zurück, die iterierbare Iteratoren sind. Asynchrone Generatorfunktionen geben asynchrone Generatorobjekte zurück, die asynchrone iterierbare Iteratoren sind.

Die von eingebauten Iterables zurückgegebenen Iteratoren erben tatsächlich alle von einer gemeinsamen Klasse Iterator, die die oben erwähnte [Symbol.iterator]() { return this; } Methode implementiert, was sie zu allen iterierbaren Iteratoren macht. Die Iterator-Klasse bietet zusätzlich zu der next()-Methode, die vom Iteratorprotokoll gefordert wird, weitere Hilfsmethoden an. Sie können die Prototypenkette eines Iterators inspizieren, 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.prototype

Eingebaute APIs, die Iterables akzeptieren

Es gibt viele APIs, die Iterables akzeptieren. Einige Beispiele sind:

js
const myObj = {};

new WeakSet(
  (function* () {
    yield {};
    yield myObj;
    yield {};
  })(),
).has(myObj); // true

Syntaxen, die Iterables erwarten

Einige Anweisungen und Ausdrücke erwarten Iterables, zum Beispiel die for...of Schleifen, Array- und Parameter-Ausbreitung, yield*, und Array-Destrukturierung:

js
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 Ergebnis done false ist (d.h. der Iterator könnte mehr Werte erzeugen), aber keine weiteren Werte benötigt werden, wird die return Methode aufgerufen, wenn sie vorhanden ist. Dies kann zum Beispiel passieren, wenn ein break oder return in einer for...of Schleife auftritt oder wenn alle Bezeichner bereits in einer Array-Destrukturierung gebunden sind.

js
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
// Closing

const [b, c, d] = obj;
// Returning 1
// Returning 2
// Returning 3
// Already reached the end (the last call returned `done: true`),
// so `return` is not called
console.log([b, c, d]); // [1, 2, undefined]; the value associated with `done: true` is not reachable

for (const b of obj) {
  break;
}
// Returning 1
// Closing

Die for await...of Schleife und yield* in asynchronen Generatorfunktionen (aber nicht synchronen Generatorfunktionen) sind die einzigen Möglichkeiten, mit asynchronen Iterables zu interagieren. Die Verwendung von for...of, Array-Ausbreitung, usw. auf einem asynchronen Iterable, das nicht auch ein synchrones Iterable ist (d.h. es hat [Symbol.asyncIterator]() aber kein [Symbol.iterator]()), führt zu einem TypeError: x is not iterable.

Fehlerbehandlung

Da die Iteration die Kontrolle zwischen dem Iterator und dem Verbraucher hin- und her überträgt, erfolgt die Fehlerbehandlung in beide Richtungen: wie der Verbraucher mit Fehlern umgeht, die vom Iterator geworfen werden, und wie der Iterator mit Fehlern umgeht, die vom Verbraucher geworfen werden. Wenn Sie eine der eingebauten Methoden zur Iteration verwenden, kann die Sprache auch Fehler auslösen, weil das Iterable bestimmte Invarianten verletzt. Wir beschreiben, wie eingebaute Syntaxen Fehler erzeugen und behandeln, die als Richtlinie für Ihren eigenen Code dienen können, wenn Sie den Iterator manuell durchlaufen.

Nicht richtig geformte Iterables

Fehler können auftreten, wenn der Iterator vom Iterable erworben wird. Die hier durchgesetzte Sprache-Invariante ist, dass das Iterable einen gültigen Iterator erzeugen 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 aufrufbare next() Methode.

Wenn eingebaute Syntax verwendet wird, um eine Iteration auf einem nicht richtig geformten Iterable zu starten, wird ein TypeError ausgelöst.

js
const nonWellFormedIterable = { [Symbol.iterator]: 1 };
[...nonWellFormedIterable]; // TypeError: nonWellFormedIterable is not iterable
nonWellFormedIterable[Symbol.iterator] = () => 1;
[...nonWellFormedIterable]; // TypeError: [Symbol.iterator]() returned a non-object value
nonWellFormedIterable[Symbol.iterator] = () => ({});
[...nonWellFormedIterable]; // TypeError: nonWellFormedIterable[Symbol.iterator]().next is not a function

Für asynchrone Iterables, wenn seine [Symbol.asyncIterator]() Eigenschaft den Wert undefined oder null hat, fällt JavaScript darauf zurück, die [Symbol.iterator] Eigenschaft zu verwenden (und umhüllt den resultierenden Iterator in einen asynchronen Iterator durch Weiterleiten der Methoden). Andernfalls muss die [Symbol.asyncIterator] Eigenschaft ebenfalls die oben genannten Invarianten einhalten.

Diese Art von Fehlern kann verhindert werden, indem das Iterable vor dem Versuch es zu durchlaufen, validiert wird. Es ist jedoch ziemlich selten, weil Sie normalerweise wissen, um was für ein Objekt es sich handelt, das Sie durchlaufen. Wenn Sie dieses Iterable von einem anderen Code erhalten, sollten Sie den Fehler an den Aufrufer weiterleiten, damit er weiß, dass eine ungültige Eingabe bereitgestellt wurde.

Fehler während der Iteration

Die meisten Fehler treten auf, wenn der Iterator weitergeschaltet wird (indem next() aufgerufen wird). Die hier durchgesetzte Sprache-Invariante ist, dass die next() Methode ein Objekt zurückgeben muss (für asynchrone Iteratoren, ein Objekt nach dem Awaiting). Andernfalls wird ein TypeError ausgelöst.

Wenn die Invariante verletzt wird oder die next() Methode einen Fehler wirft (für asynchrone Iteratoren kann es auch ein abgelehntes Promise zurückgeben), wird der Fehler an den Aufrufer weitergegeben. Bei eingebauten Syntaxen wird die laufende Iteration ohne Wiederholung oder Bereinigung abgebrochen (mit der Annahme, dass wenn die next() Methode den Fehler ausgelöst hat, diese bereits gereinigt hat). Wenn Sie next() manuell aufrufen, können Sie den Fehler abfangen und next() erneut aufrufen, aber im Allgemeinen sollten Sie davon ausgehen, dass der Iterator bereits geschlossen ist.

Wenn der Aufrufer aus irgendeinem anderen Grund als den in den vorherigen Absätzen genannten Fehlern beschließt, die Iteration zu beenden, wie zum Beispiel wenn er in seinem eigenen Code einen Fehlerzustand eingeht (zum Beispiel beim Umgang mit einem ungültigen Wert, der vom Iterator erzeugt wurde), sollte er die return() Methode des Iterators aufrufen, wenn eine vorhanden ist. Dies ermöglicht dem Iterator, alle notwendigen Bereinigungen durchzuführen. Die return() Methode wird nur für vorzeitige Abbrüche aufgerufen—wenn next() done: true zurückgibt, wird die return() Methode nicht aufgerufen, in der Annahme, dass der Iterator bereits gereinigt hat.

Auch die return() Methode könnte ungültig sein! Die Sprache erzwingt auch, dass die return() Methode ein Objekt zurückgibt und wirft andernfalls einen TypeError. Wenn die return() Methode einen Fehler wirft, wird der Fehler an den Aufrufer weitergegeben. Wenn die return() Methode jedoch aufgerufen wird, weil der Aufrufer in seinem eigenen Code einen Fehler entdeckt hat, überschreibt dieser Fehler den durch die return() Methode geworfenen Fehler.

Der Verbraucher implementiert in der Regel die Fehlerbehandlung wie folgt:

js
try {
  for (const value of iterable) {
    // …
  }
} catch (e) {
  // Handle the error
}

Der catch kann Fehler abfangen, die ausgelöst werden, wenn iterable kein gültiges Iterable ist, wenn next() einen Fehler wirft, wenn return() einen Fehler wirft (wenn die for Schleife frühzeitig endet), und wenn der for Schleifenrumpf einen Fehler wirft.

Die meisten Iteratoren werden mit Generatorfunktionen implementiert, daher zeigen wir, wie Generatorfunktionen typischerweise Fehler behandeln:

js
function* gen() {
  try {
    yield doSomething();
    yield doSomethingElse();
  } finally {
    cleanup();
  }
}

Das Fehlen eines catch hier lässt Fehler, die durch doSomething() oder doSomethingElse() geworfen werden, an den Aufrufer von gen propagieren. Wenn diese Fehler innerhalb der Generatorfunktion abgefangen werden (was ebenfalls ratsam ist), kann die Generatorfunktion entscheiden, ob sie weiterhin Werte liefert oder frühzeitig beendet. Der finally Block ist jedoch notwendig für Generatoren, die offene Ressourcen halten. Der finally Block wird garantiert ausgeführt, entweder wenn das letzte next() aufgerufen wird oder wenn return() aufgerufen wird.

Fehler Weiterleiten

Einige eingebaute Syntaxen umhüllen einen Iterator in einen anderen Iterator. Dazu gehören der Iterator, der von Iterator.from() erzeugt wird, Iterator-Hilfsmethoden (map(), filter(), take(), drop(), und flatMap()), yield*, und ein versteckter Wrapper, wenn Sie asynchrone Iteration (for await...of, Array.fromAsync) auf synchronen Iteratoren verwenden. Der umhüllte Iterator ist dann dafür verantwortlich, Fehler zwischen dem inneren Iterator und dem Verbraucher weiterzuleiten.

  • Wrapper-Iteratoren leiten im Allgemeinen direkt die next() Methode des inneren Iterators weiter, einschließlich seines Rückgabewertes und geworfener Fehler. Der asynchrone Wrapper eines synchronen Iterators wartet die value-Eigenschaft des zurückgegebenen Objekts ab und konvertiert geworfene Fehler in Promise-Ablehnungen. Wenn ein geliefertes Promise abgelehnt wird, ruft der Wrapper die return() Methode des inneren Iterators auf, falls eine vorhanden ist, bevor er sein eigenes Promise ablehnt. Wenn die next() Methode des inneren Iterators selbst einen Fehler wirft, lehnt der Wrapper sein Promise ab, ohne return() aufzurufen.
  • Wrapper-Iteratoren leiten im Allgemeinen direkt die return() Methode des inneren Iterators weiter. Wenn die return() Methode nicht auf dem inneren Iterator existiert, gibt sie stattdessen { done: true, value: undefined } zurück. Im Falle von Iterator-Helpern: Wenn die next() Methode des Iterator-Helpers nicht aufgerufen wurde, gibt der aktuelle Iterator nach dem Versuch, die return() Methode des inneren Iterators aufzurufen, immer { done: true, value: undefined } zurück. Dies ist konsistent mit Generatorfunktionen, bei denen die Ausführung den yield* Ausdruck noch nicht erreicht hat.
  • yield* ist die einzige eingebaute Syntax, die die throw() Methode des inneren Iterators weiterleitet. Weitere Informationen dazu, wie yield* die return() und throw() Methoden weiterleitet, finden Sie in der entsprechende Referenz.

Beispiele

Benutzerdefinierte Iterables

Sie können Ihre eigenen Iterables auf diese Weise erstellen:

js
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 als Generatorfunktion definieren (wie das obige Beispiel zeigt), möchten Sie wahrscheinlich den Zustand in einer Schließung kapseln.

js
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); // true

Unendlicher Iterator

js
function idMaker() {
  let index = 0;
  return {
    next() {
      return {
        value: index++,
        done: false,
      };
    },
  };
}

const it = idMaker();

console.log(it.next().value); // 0
console.log(it.next().value); // 1
console.log(it.next().value); // 2
// …

Definieren eines Iterables mit einem Generator

js
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); // true

function* idMaker() {
  let index = 0;
  while (true) {
    yield index++;
  }
}

const it = idMaker();

console.log(it.next().value); // 0
console.log(it.next().value); // 1
console.log(it.next().value); // 2
// …

Definieren eines Iterables mit einer Klasse

Die Zustandkapselung kann auch mit privaten Feldern durchgeführt werden.

js
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

Zum Beispiel ist ein String ein eingebautes iterierbares Objekt:

js
const someString = "hi";
console.log(typeof someString[Symbol.iterator]); // "function"

Der Standard-Iterator von String gibt die Codepunkte des Strings nacheinander zurück:

js
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 unser eigenes [Symbol.iterator]() bereitstellen:

js
// need to construct a String object explicitly to avoid auto-boxing
const 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 das Neu-Definieren von [Symbol.iterator]() das Verhalten von eingebauten Konstrukten beeinflusst, die das Iterationsprotokoll verwenden:

js
console.log([...someString]); // ["bye"]
console.log(`${someString}`); // "hi"

Gleichzeitige Änderungen beim Iterieren

Fast alle Iterables haben das gleiche zugrunde liegende Konzept: Sie kopieren die Daten nicht zu dem Zeitpunkt, wenn die Iteration beginnt. Stattdessen halten sie einen Zeiger und bewegen ihn. Daher können, wenn Sie Elemente in der Sammlung hinzufügen, löschen oder ändern, während Sie die Sammlung durchlaufen, unbeabsichtigt ändern, ob andere unveränderte Elemente in der Sammlung besucht werden. Dies ist sehr ähnlich wie die iterativen Array Methoden funktionieren.

Betrachten Sie den folgenden Fall mit einem URLSearchParams:

js
const searchParams = new URLSearchParams(
  "deleteme1=value1&key2=value2&key3=value3",
);

// Delete unwanted keys
for (const [key, value] of searchParams) {
  console.log(key);
  if (key.startsWith("deleteme")) {
    searchParams.delete(key);
  }
}

// Output:
// deleteme1
// key3

Beachten Sie, wie niemals key2 protokolliert wird. Dies ist, weil ein URLSearchParams zugrunde liegend eine Liste von Schlüssel-Wert-Paaren ist. Wenn deleteme1 besucht und gelöscht wird, werden alle anderen Einträge um eins nach links verschoben, so dass key2 die Position einnimmt, die deleteme1 hatte, und wenn sich der Zeiger zum nächsten Schlüssel bewegt, landet er auf key3.

Bestimmte iterable Implementationen vermeiden dieses Problem, indem sie "Grabsteine" setzen, um das Verschieben der verbleibenden Werte zu vermeiden. Betrachten Sie den ähnlichen Code mit einem Map:

js
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
// key3

Beachten Sie, wie es alle Schlüssel protokolliert. Dies liegt daran, dass Map die verbleibenden Schlüssel nicht verschiebt, wenn einer gelöscht wird. Wenn Sie etwas Ähnliches implementieren möchten, könnte es so aussehen:

js
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. Es ist am besten, das Ändern der Sammlung beim Durchlaufen zu vermeiden, es sei denn, Sie wissen genau, wie das Iterable implementiert ist.

Spezifikationen

Spezifikation
ECMAScript® 2027 Language Specification
# sec-iteration

Siehe auch