Iterators en generators in JavaScript gebruiken om code te optimaliseren

Invoering

Heb je ooit een lijst moeten doorlopen, maar de operatie duurde behoorlijk lang? Heeft u ooit een programma-crash gehad omdat een bewerking te veel geheugen heeft gebruikt? Dit overkwam mij toen ik probeerde een functie te implementeren die priemgetallen genereert. 

Het genereren van priemgetallen tot een miljoen kostte veel meer tijd dan ik had gewild. Maar het genereren van cijfers tot 10 miljoen was onmogelijk. Mijn programma zou crashen of gewoon hangen. Ik gebruikte al de zeef van Eratosthenes, die efficiënter zou moeten zijn in het genereren van prime-lenzen dan de brute force-benadering.

Als u zich in een vergelijkbare situatie bevindt, kunt u proberen een ander algoritme te gebruiken. Er zijn zoek- en sorteeralgoritmen die beter werken op grotere ingangen. Het nadeel is dat die algoritmen misschien moeilijker te begrijpen zijn. Een andere optie is om een ​​andere programmeertaal te gebruiken. 

Een gecompileerde taal kan de code aanzienlijk sneller verwerken. Maar een andere taal gebruiken is misschien niet praktisch. Je kunt ook meerdere threads gebruiken. Nogmaals, het is misschien niet praktisch omdat je programmeertaal dit zou moeten ondersteunen.

Gelukkig is er met JavaScript een andere optie. Als u een rekenintensieve taak hebt, kunt u iterators en generators gebruiken om enige efficiëntie te bereiken. Iterators zijn eigendom van bepaalde JavaScript-collecties. 

Iterators verbeteren de efficiëntie door u de items in een lijst één voor één te laten consumeren alsof het een stream is. Generators zijn een speciaal soort functie waarmee de uitvoering kan worden onderbroken. Door een generator aan te roepen, kunt u gegevens één brok per keer produceren zonder eerst in een lijst op te slaan.

iterators

Laten we eerst de verschillende manieren bekijken waarop u door collecties in JavaScript kunt bladeren. Een lus van het formulier voor (initiële; voorwaarde; stap) ... zal de commando's een bepaald aantal keren in zijn lichaam uitvoeren. Op dezelfde manier zal een while-lus de commando's in zijn lichaam uitvoeren zolang de voorwaarde waar is. 

U kunt deze lussen gebruiken om een ​​lijst te doorlopen door bij elke iteratie een indexvariabele te verhogen. Een iteratie is een uitvoering van het lichaam van een lus. Deze lussen kennen de structuur van uw lijst niet. Ze fungeren als loketten.

EEN voor in loop en a voor / van loop zijn ontworpen om specifieke datastructuren te herhalen. Itereren over een datastructuur betekent dat je door elk element heen stapt. EEN voor in lus itereert over de toetsen in een gewoon JavaScript-object. EEN voor / van lus itereert over de waarden van een iterabele. Wat is een iterable? Simpel gezegd, een iterabele is een object dat een iterator heeft. Voorbeelden van iterables zijn arrays en sets. De iterator is een eigenschap van het object die een mechanisme biedt voor het doorlopen van het object.

Wat een iterator-special speciaal maakt, is hoe het een verzameling doorloopt. Andere loops moeten de hele verzameling vooraan laden om deze te itereren, terwijl een iterator alleen de huidige positie in de verzameling hoeft te weten. 

U opent het huidige item door de volgende methode van de iterator aan te roepen. De volgende methode retourneert de waarde van het huidige item en een boolean om aan te geven wanneer u het einde van de verzameling hebt bereikt. Het volgende is een voorbeeld van het maken van een iterator uit een 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

U kunt ook de waarden van de iterator herhalen met behulp van a voor / van lus. Gebruik deze methode wanneer u weet dat u toegang wilt tot alle items in het object. Dit is hoe je een lus zou gebruiken om door de vorige lijst te bladeren:

for (const elem of it) console.log (elem); 

Waarom zou u een iterator gebruiken? Het gebruik van een iterator is gunstig wanneer de berekeningskosten voor het verwerken van een lijst hoog zijn. Als u een gegevensbron hebt die erg groot is, kan dit problemen veroorzaken in uw programma als u dit probeert te herhalen omdat de hele verzameling moet worden geladen. 

Met een iterator kunt u de gegevens laden in chunks. Dit is efficiënter omdat u alleen het gedeelte van de lijst manipuleert dat u nodig hebt, zonder dat dit de extra kosten voor de verwerking van de volledige lijst met zich meebrengt. 

Een voorbeeld kan zijn dat u gegevens uit een bestand of database hebt geladen en dat u de informatie op het scherm geleidelijk wilt weergeven. U kunt een iterator van de gegevens maken en een gebeurtenishandler instellen om een ​​paar items te pakken elke keer dat de gebeurtenis plaatsvindt. Dit is een voorbeeld van hoe een dergelijke implementatie eruit zou kunnen zien:

laat berichten = laden (url); let it = berichten [Symbol.iterator] (); functie loadPosts (iterable, count) for (let i = 0; i < count; i++)  display(iterable.next().value);   document.getElementById('btnNext').onclick = loadPosts(it, 5);

generatoren

Als je een verzameling wilt bouwen, kun je dat doen met een generator. Een generatorfunctie kan waarden één voor één retourneren door de uitvoering bij elke iteratie te onderbreken. Wanneer u een exemplaar van een generator maakt, zijn deze items toegankelijk via een iterator. Dit is de algemene syntaxis voor het maken van een generatorfunctie:

function * genFunc () ... opbrengstwaarde; 

De * betekent dat dit een generatorfunctie is. De opbrengst keyword pauzeert onze functie en levert op dat moment de status van de generator. Waarom zou je een generator gebruiken? U zou een generator gebruiken wanneer u algoritmisch een waarde in een verzameling wilt produceren. Het is vooral handig als u een zeer grote of oneindige verzameling heeft. Laten we een voorbeeld bekijken om te begrijpen hoe dit ons helpt. 

Stel dat je een online poolgame hebt die je hebt gebouwd en je wilt spelers koppelen aan gamekamers. Je doel is om alle manieren te genereren waarop je twee verschillende spelers uit je lijst met 2000 gamers kunt kiezen. De combinaties voor twee spelers die in de lijst zijn gegenereerd ['a', 'b', 'c', 'd'] zou zijn ab, ac, ad, bc, bd, cd.  Dit is een oplossing met behulp van geneste lussen:

functiecombinaties (lijst) const n = list.length; laat resultaat = []; for (let 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']));

Probeer nu de functie uit te voeren met een lijst van 2000 elementen. (U kunt uw lijst initialiseren met een for-lus die de getallen 1 tot 2.000 aan een array toevoegt). Wat gebeurt er nu wanneer je je code uitvoert? 

Wanneer ik de code in een online-editor gebruik, loopt de webpagina vast. Wanneer ik het uitprobeer in de console in Chrome, zie ik dat de uitvoer langzaam wordt afgedrukt. De CPU van mijn computer begint echter overdrive te worden en ik moet stoppen met Chrome. Dit is de herziene code met behulp van een generatorfunctie:

function * combo's (lijst) const n = list.length; for (let 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(); 

Een ander voorbeeld is als we de getallen in de Fibonacci-reeks tot in het oneindige wilden genereren. Hier is een implementatie:

function * fibGen () laat stroom = 0; laat volgende = 1; while (true) yield current; laat nextNum = current + next; current = volgende; next = nextNum;  laat het = fibGen (); . It.next () waarde; // 0 it.next (). Waarde; // 1 it.next (). Waarde; // 1 it.next (). Waarde; // 2

Normaal gesproken zal een oneindige lus uw programma doen crashen. De fibGen functie is in staat om voor altijd te draaien omdat er geen stopconditie is. Maar omdat het een generator is, bepaalt u wanneer elke stap wordt uitgevoerd.

Beoordeling

Iterators en generatoren zijn handig wanneer u een verzameling stapsgewijs wilt verwerken. U wint efficiëntie door de staat van de verzameling bij te houden in plaats van alle items in de verzameling. Items in de collectie worden één voor één geëvalueerd en de evaluatie van de rest van de collectie wordt uitgesteld tot later. 

Iterators bieden een efficiënte manier om grote lijsten te doorkruisen en te manipuleren. Generators bieden een efficiënte manier om lijsten samen te stellen. Je zou deze technieken moeten uitproberen wanneer je anders een complex algoritme zou gebruiken of parallel programmeren zou implementeren om je code te optimaliseren.

Als u op zoek bent naar extra middelen om te studeren of om te gebruiken in uw werk, kijk dan wat we beschikbaar hebben op de Envato-markt.

Leer JavaScript: de complete gids

We hebben een complete handleiding samengesteld om u te helpen JavaScript te leren, of u net bent begonnen als een webontwikkelaar of dat u meer geavanceerde onderwerpen wilt verkennen.

Middelen

  • Ontwerppatronen: elementen van herbruikbare objectgeoriënteerde software: Iteratorpatroon
  • ECMAScript Specificatie voor Iterators en generatoren
  • Structuur en interpretatie van computerprogramma's - Hoofdstuk 3.5: Streams