Använda Iteratorer och Generatorer i JavaScript för att optimera koden

Introduktion

Har du någonsin behövt gå igenom en lista, men operationen tog en betydande tid att slutföra? Har du någonsin haft en programkrasch eftersom en operation använde för mycket minne? Detta hände mig när jag försökte implementera en funktion som genererar primtal. 

Att generera primtal upp till en miljon tog mycket mer tid än jag skulle ha velat. Men att generera siffror upp till 10 miljoner var omöjligt. Mitt program skulle krascha eller bara hänga. Jag använde redan sikten av Eratosthenes, som är avsedd att vara mer effektiv vid generering av primer än den brute force approachen.

Om du befinner dig i en liknande situation kan du försöka använda en annan algoritm. Det finns söka och sortera algoritmer som fungerar bättre på större ingångar. Nackdelen är att algoritmerna kan vara svårare att förstå omedelbart. Ett annat alternativ är att använda ett annat programmeringsspråk. 

Ett sammanställt språk kan kunna hantera koden betydligt snabbare. Men det kan inte vara praktiskt att använda ett annat språk. Du kan också försöka använda flera trådar. Återigen kan det inte vara praktiskt eftersom ditt programmeringsspråk skulle behöva stödja detta.

Lyckligtvis med JavaScript finns det ett annat alternativ. Om du har en beräkningsintensiv uppgift kan du använda iteratorer och generatorer för att få lite effektivitet. Iteratorer är en egenskap av vissa JavaScript-samlingar. 

Iteratorer förbättrar effektiviteten genom att låta dig konsumera objekten i en lista en i taget som om de var en ström. Generatorer är en speciell typ av funktion som kan pausa körning. Med en generator kan du producera data en bit i taget utan att behöva lagra den i en lista först.

iteratorer

Låt oss först och främst granska de olika sätten att lösa genom samlingar i JavaScript. En slinga i formen för (inledande, villkor, steg) ... kommer att utföra kommandon i sin kropp ett visst antal gånger. På samma sätt kommer en stundslinga att genomföra kommandon i sin kropp så länge som dess tillstånd är sant. 

Du kan använda dessa loopar för att korsa en lista genom att öka en indexvariabel vid varje iteration. En iteration är ett utförande av en slingans kropp. Dessa slingor vet inte om strukturen på din lista. De fungerar som räknare.

en för / i loop och a för av loop är utformad för att iterera över specifika datastrukturer. Iterterar över en datastruktur innebär att du går igenom varje element. en för / i slinga iterates över nycklarna i ett vanlig JavaScript-objekt. en för av loop löser över värdena på en iterbar. Vad är en iterbar? Enkelt uttryckt är en iterbar ett objekt som har en iterator. Exempel på iterables är arrays och uppsättningar. Iteratorn är en egenskap hos objektet som ger en mekanism för att kryssa objektet.

Vad som gör en iterator speciell är hur det går igenom en samling. Andra slingor måste ladda hela samlingen upp för att iterera över den, medan en iterator bara behöver veta den nuvarande positionen i samlingen. 

Du får tillgång till det aktuella objektet genom att ringa iteratorens nästa metod. Nästa metod returnerar värdet på det aktuella objektet och en booleska för att indikera när du har nått slutet av samlingen. Följande är ett exempel på att skapa en iterator från en array.

const alfa = ['a', 'b', 'c']; const det = alfa [symbol.iterator] (); it.next (); // värde: 'a', gjort: false it.next (); // värde: 'b', gjort: false it.next (); // värde: 'c', gjort: false it.next (); // värde: odefinierat, gjort: true

Du kan också iterera över värdena på iteratorn med hjälp av a för av slinga. Använd den här metoden när du vet att du vill komma åt alla objekt i objektet. Så här använder du en slinga för att iterera genom föregående lista:

för (konst det) console.log (elem); 

Varför skulle du använda en iterator? Att använda en iterator är fördelaktigt när beräkningskostnaden för behandling av en lista är hög. Om du har en stor stor datakälla kan det orsaka problem i ditt program om du försöker iterera över det eftersom hela samlingen måste laddas. 

Med en iterator kan du ladda data i bitar. Detta är effektivare eftersom du bara manipulerar den del av listan som du behöver utan att medföra extra kostnader för att bearbeta hela listan. 

Ett exempel kan vara att du har laddat data från en fil eller en databas, och du vill gradvis visa informationen på skärmen. Du kan skapa en iterator från data och skapa en händelsehanterare för att fånga några saker varje gång händelsen inträffar. Detta är ett exempel på hur en sådan implementering kan se ut:

låt inlägg = ladda (url); låt det = inlägg [Symbol.iterator] (); funktion loadPosts (iterable, count) for (låt i = 0; i < count; i++)  display(iterable.next().value);   document.getElementById('btnNext').onclick = loadPosts(it, 5);

generatorer

Om du vill bygga en samling kan du göra det med en generator. En generatorfunktion kan returnera värden en åt gången genom att pausa körningen vid varje iteration. När du skapar en instans av en generator kan du komma åt dessa objekt med hjälp av en iterator. Det här är den allmänna syntaxen för att skapa en generatorfunktion:

funktion * genFunc () ... avkastningsvärde; 

De * betyder att det här är en generatorfunktion. De avkastning nyckelordet pausar vår funktion och levererar generatorens tillstånd vid det aktuella ögonblicket. Varför skulle du använda en generator? Du skulle använda en generator när du algoritmiskt producerar ett värde i en samling. Det är särskilt användbart om du har en mycket stor eller oändlig samling. Låt oss titta på ett exempel för att förstå hur det hjälper oss. 

Antag att du har ett online poolspel du har byggt, och du vill matcha spelare till spelrum. Ditt mål är att generera alla sätt du kan välja två distinkta spelare från din lista med 2 000 spelare. De två spelare kombinationer som genereras från listan ['a', 'b', 'c', 'd'] skulle vara ab, ac, annons, bc, bd, cd.  Det här är en lösning med kapslingar:

funktionskombinationer (lista) const n = list.length; låt resultatet = []; för (låt jag = 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']));

Försök nu att utföra funktionen med en lista med 2000 element. (Du kan initiera din lista med en för loop som lägger till siffrorna 1 till 2000 i en array). Vad händer nu när du kör din kod? 

När jag kör koden i en online-editor kraschar websidan. När jag provar det i konsolen i Chrome kan jag se utskriften sakta utskrift. Däremot börjar min dator CPU att gå in i overdrive, och jag måste tvinga sluta Chrome. Detta är den reviderade koden med en generatorfunktion:

funktion * combos (lista) const n = list.length; för (låt jag = 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(); 

Ett annat exempel är om vi ville generera siffrorna i Fibonacci-sekvensen upp till oändligheten. Här är en implementering:

funktion * fibGen () låt nuvarande = 0; låt nästa = 1; medan (sant) yield current; låt nextNum = current + next; nuvarande = nästa; nästa = nextNum;  låt det = fibGen (); . It.next () värde; // 0 det.next (). Värde; // 1 det.next (). Värde; // 1 det.next (). Värde; // 2

Normalt kommer en oändlig loop att krascha ditt program. De fibGen funktionen kan springa för alltid eftersom det inte finns något stoppförhållande. Men eftersom det är en generator, kontrollerar du när varje steg utförs.

Recension

Iteratorer och generatorer är till nytta när du vill bearbeta en samling inkrementellt. Du får effektivitet genom att hålla reda på samlingens tillstånd istället för alla objekt i samlingen. Objekt i samlingen utvärderas en i taget, och utvärderingen av resten av samlingen försenas till senare. 

Iteratorer ger ett effektivt sätt att kryssa och manipulera stora listor. Generatorer ger ett effektivt sätt att bygga listor. Du bör prova dessa tekniker när du annars skulle använda en komplex algoritm eller implementera parallell programmering för att optimera din kod.

Om du letar efter ytterligare resurser att studera eller använda i ditt arbete, kolla vad vi har tillgängligt på Envato-marknaden.

Lär dig JavaScript: Den fullständiga guiden

Vi har byggt en komplett guide för att hjälpa dig att lära dig JavaScript, oavsett om du precis börjat som webbutvecklare eller vill utforska mer avancerade ämnen.

Medel

  • Designmönster: Element av återanvändbar objektorienterad programvara: Iteratormönster
  • ECMAScript Specifikation för Iteratorer och Generatorer
  • Struktur och tolkning av datorprogram - Kapitel 3.5: Strömmar