Verwendung von Iteratoren und Generatoren in JavaScript zur Optimierung von Code

Einführung

Haben Sie schon einmal eine Liste durchlaufen müssen, aber der Vorgang hat viel Zeit in Anspruch genommen? Haben Sie jemals einen Programmabsturz gehabt, weil eine Operation zu viel Speicher beanspruchte? Dies ist mir passiert, als ich versuchte, eine Funktion zu implementieren, die Primzahlen generiert. 

Die Generierung von Primzahlen bis zu einer Million hat viel mehr Zeit gekostet, als ich mir gewünscht hätte. Es war jedoch unmöglich, Zahlen von bis zu 10 Millionen zu generieren. Mein Programm stürzt ab oder hängt einfach. Ich habe bereits das Eratosthenes-Sieb verwendet, das Primzahlen effizienter erzeugen soll als die Brute-Force-Methode.

Wenn Sie sich in einer ähnlichen Situation befinden, können Sie einen anderen Algorithmus verwenden. Es gibt Such- und Sortieralgorithmen, die bei größeren Eingaben besser funktionieren. Der Nachteil ist, dass diese Algorithmen sofort schwieriger zu erfassen sind. Eine andere Option ist die Verwendung einer anderen Programmiersprache. 

Eine kompilierte Sprache kann den Code möglicherweise wesentlich schneller verarbeiten. Die Verwendung einer anderen Sprache ist jedoch möglicherweise nicht praktikabel. Sie können auch versuchen, mehrere Threads zu verwenden. Auch dies ist möglicherweise nicht praktisch, da Ihre Programmiersprache dies unterstützen müsste.

Glücklicherweise gibt es mit JavaScript eine andere Option. Wenn Sie eine rechenintensive Aufgabe haben, können Sie Iteratoren und Generatoren verwenden, um eine gewisse Effizienz zu erzielen. Iteratoren sind eine Eigenschaft bestimmter JavaScript-Sammlungen. 

Iteratoren verbessern die Effizienz, indem Sie die Elemente in einer Liste nacheinander konsumieren lassen, als ob sie ein Stream wären. Generatoren sind eine spezielle Funktion, die die Ausführung unterbrechen kann. Durch Aufrufen eines Generators können Sie Daten stückweise erstellen, ohne sie zuerst in einer Liste speichern zu müssen.

Iteratoren

Sehen wir uns zunächst die verschiedenen Möglichkeiten an, wie Sie Sammlungen in JavaScript durchlaufen können. Eine Schleife des Formulars für (Anfang; Bedingung; Schritt) … führt die Befehle in seinem Körper eine bestimmte Anzahl von Malen aus. In ähnlicher Weise führt eine while-Schleife die Befehle in ihrem Hauptteil aus, solange die Bedingung erfüllt ist. 

Sie können diese Schleifen verwenden, um eine Liste zu durchlaufen, indem Sie bei jeder Iteration eine Indexvariable inkrementieren. Eine Iteration ist eine Ausführung des Körpers einer Schleife. Diese Schleifen wissen nichts über die Struktur Ihrer Liste. Sie wirken als Zähler.

EIN für in Schleife und a für / von Schleife soll bestimmte Datenstrukturen durchlaufen. Wenn Sie eine Datenstruktur durchlaufen, bedeutet das, dass Sie die einzelnen Elemente durchlaufen. EIN für in Die Schleife durchläuft die Schlüssel in einem einfachen JavaScript-Objekt. EIN für / von Schleife iteriert über die Werte eines iterierbaren. Was ist ein Iterer? Einfach gesagt, ein Iterer ist ein Objekt, das einen Iterator hat. Beispiele für iterierbare Elemente sind Arrays und Sets. Der Iterator ist eine Eigenschaft des Objekts, die einen Mechanismus zum Durchlaufen des Objekts bereitstellt.

Das Besondere an einem Iterator ist das Durchlaufen einer Sammlung. Andere Schleifen müssen die gesamte Sammlung nach vorne laden, um darüber zu iterieren, während ein Iterator nur die aktuelle Position in der Sammlung kennen muss. 

Sie greifen auf das aktuelle Element zu, indem Sie die nächste Methode des Iterators aufrufen. Die nächste Methode gibt den Wert des aktuellen Elements und einen Booleschen Wert zurück, um anzuzeigen, wann Sie das Ende der Auflistung erreicht haben. Das folgende Beispiel zeigt das Erstellen eines Iterators aus einem Array.

const alpha = ['a', 'b', 'c']; const it = alpha [Symbol.iterator] (); it.next (); // value: 'a', done: false it.next (); // value: 'b', done: false it.next (); // value: 'c', done: false it.next (); // value: undefined, done: true

Sie können die Werte des Iterators auch mit a durchlaufen für / von Schleife. Verwenden Sie diese Methode, wenn Sie wissen, dass Sie auf alle Elemente im Objekt zugreifen möchten. So würden Sie eine Schleife verwenden, um die vorherige Liste zu durchlaufen:

für (const elem davon) console.log (elem); 

Warum verwenden Sie einen Iterator? Die Verwendung eines Iterators ist vorteilhaft, wenn der Rechenaufwand für die Verarbeitung einer Liste hoch ist. Wenn Sie über eine sehr große Datenquelle verfügen, kann es zu Problemen in Ihrem Programm kommen, wenn Sie versuchen, es zu durchlaufen, da die gesamte Sammlung geladen werden muss. 

Mit einem Iterator können Sie die Daten in Chunks laden. Dies ist effizienter, da Sie nur den von Ihnen benötigten Teil der Liste bearbeiten, ohne dass zusätzliche Kosten für die Verarbeitung der gesamten Liste entstehen. 

Ein Beispiel könnte sein, dass Sie Daten aus einer Datei oder Datenbank geladen haben und die Informationen schrittweise auf dem Bildschirm anzeigen möchten. Sie können aus den Daten einen Iterator erstellen und einen Event-Handler einrichten, der bei jedem Auftreten des Ereignisses einige Elemente abruft. Dies ist ein Beispiel, wie eine solche Implementierung aussehen könnte:

Lass posts = load (url); let it = posts [Symbol.iterator] (); Funktion loadPosts (iterable, count) für (sei i = 0; i < count; i++)  display(iterable.next().value);   document.getElementById('btnNext').onclick = loadPosts(it, 5);

Generatoren

Wenn Sie eine Sammlung erstellen möchten, können Sie dies mit einem Generator tun. Eine Generatorfunktion kann Werte einzeln zurückgeben, indem die Ausführung bei jeder Iteration angehalten wird. Wenn Sie eine Instanz eines Generators erstellen, kann auf diese Elemente mit einem Iterator zugegriffen werden. Dies ist die allgemeine Syntax zum Erstellen einer Generatorfunktion:

function * genFunc () … Ergebniswert; 

Das * bedeutet, dass dies eine Generatorfunktion ist. Das Ausbeute Das Schlüsselwort pausiert unsere Funktion und liefert den Status des Generators zu diesem Zeitpunkt. Warum verwenden Sie einen Generator? Sie würden einen Generator verwenden, wenn Sie einen Wert in einer Sammlung algorithmisch erzeugen möchten. Dies ist besonders nützlich, wenn Sie eine sehr große oder unendliche Sammlung haben. Schauen wir uns ein Beispiel an, um zu verstehen, wie uns das hilft. 

Angenommen, Sie haben ein Online-Poolspiel, das Sie erstellt haben, und Sie möchten die Spieler den Spielräumen zuordnen. Ihr Ziel ist es, alle Möglichkeiten zu generieren, wie Sie zwei verschiedene Spieler aus Ihrer Liste von 2.000 Spielern auswählen können. Die aus der Liste generierten Zwei-Spieler-Kombinationen ['A B C D'] wäre ab, ac, ad, bc, bd, cd.  Dies ist eine Lösung, die verschachtelte Schleifen verwendet:

Funktionskombinationen (Liste) const n = list.length; lass result = []; für (sei i = 0; i < n - 1; i++)  for (let j = i + 1; j < n; j++)  result.push([list[i], list[j]]);   return result;  console.log(combos(['a', 'b', 'c', 'd']));

Versuchen Sie nun, die Funktion mit einer Liste von 2.000 Elementen auszuführen. (Sie können Ihre Liste mit einer for-Schleife initialisieren, die einem Array die Zahlen 1 bis 2.000 hinzufügt.) Was passiert jetzt, wenn Sie Ihren Code ausführen? 

Wenn ich den Code in einem Online-Editor ausführen, stürzt die Webseite ab. Wenn ich es in Chrome in der Konsole ausprobiere, kann ich die Ausgabe langsam ausdrucken. Die CPU meines Computers beginnt jedoch in den Overdrive-Modus zu wechseln, und ich muss Chrome beenden. Dies ist der überarbeitete Code, der eine Generatorfunktion verwendet:

Funktion * Combos (Liste) const n = list.length; für (sei i = 0; i < n - 1; i++)  for (let j = i + 1; j < n; j++)  yield [list[i], list[j]];    let it = combos(['a', 'b', 'c', 'd']); it.next(); 

Ein anderes Beispiel ist, wenn wir die Zahlen in der Fibonacci-Sequenz bis unendlich erzeugen wollen. Hier ist eine Implementierung:

Funktion * fibGen () let current = 0; let next = 1; während (wahr) Fließstrom; let nextNum = current + next; current = next; next = nextNum;  Lass es = fibGen (); it.next (). value; // 0 it.next (). Value; // 1 it.next (). Value; // 1 it.next (). Value; // 2

Normalerweise stürzt eine Endlosschleife Ihr Programm ab. Das fibGen Funktion kann für immer ausgeführt werden, da keine Stoppbedingung vorliegt. Da es sich jedoch um einen Generator handelt, können Sie steuern, wann jeder Schritt ausgeführt wird.

Rezension

Iteratoren und Generatoren sind nützlich, wenn Sie eine Sammlung inkrementell bearbeiten möchten. Sie gewinnen an Effizienz, indem Sie den Status der Sammlung anstatt aller Elemente in der Sammlung verfolgen. Elemente in der Sammlung werden nacheinander ausgewertet, und die Auswertung der restlichen Sammlung wird auf später verschoben. 

Iteratoren bieten eine effiziente Möglichkeit, große Listen zu durchsuchen und zu bearbeiten. Generatoren bieten eine effiziente Möglichkeit, Listen zu erstellen. Sie sollten diese Techniken ausprobieren, wenn Sie ansonsten einen komplexen Algorithmus verwenden oder eine parallele Programmierung zur Optimierung Ihres Codes implementieren würden.

Wenn Sie nach zusätzlichen Ressourcen suchen, um zu studieren oder in Ihrer Arbeit zu verwenden, prüfen Sie, was wir auf dem Envato-Markt zur Verfügung haben.

Lernen Sie JavaScript: Das komplette Handbuch

Wir haben ein umfassendes Handbuch zusammengestellt, mit dessen Hilfe Sie JavaScript erlernen können, egal ob Sie gerade erst als Web-Entwickler anfangen oder sich mit fortgeschrittenen Themen befassen möchten.

Ressourcen

  • Design Patterns: Elemente wiederverwendbarer objektorientierter Software: Iterator Pattern
  • ECMAScript-Spezifikation für Iteratoren und Generatoren
  • Struktur und Interpretation von Computerprogrammen - Kapitel 3.5: Streams