Läsare som du hjälper till att stödja MUO. När du gör ett köp med hjälp av länkar på vår webbplats kan vi tjäna en affiliate-provision. Läs mer.

Att iterera över datainsamlingar med hjälp av traditionella loopar kan snabbt bli besvärligt och långsamt, särskilt när man hanterar enorma mängder data.

JavaScript-generatorer och iteratorer tillhandahåller en lösning för att effektivt iterera över stora datasamlingar. Med hjälp av dem kan du styra iterationsflödet, ge värden ett i taget och pausa och återuppta iterationsprocessen.

Här kommer du att täcka grunderna och insidan av en JavaScript-iterator och hur du kan generera en iterator manuellt och med hjälp av en generator.

JavaScript Iterators

En iterator är ett JavaScript-objekt som implementerar iteratorprotokollet. Dessa objekt gör det genom att ha en Nästa metod. Denna metod returnerar ett objekt som implementerar IteratorResult gränssnitt.

De IteratorResult gränssnittet består av två egenskaper: Gjort och värde. De Gjort egenskap är en boolean som återkommer

instagram viewer
falsk om iteratorn kan producera nästa värde i sin sekvens eller Sann om iteratorn har slutfört sin sekvens.

De värde egenskap är ett JavaScript-värde som returneras av iteratorn under dess sekvens. När en iterator slutför sin sekvens (när GjortSann), returnerar den här egenskapen odefinierad.

Som namnet antyder låter iteratorer dig "iterera" över JavaScript-objekt som arrayer eller kartor. Detta beteende är möjligt på grund av det itererbara protokollet.

I JavaScript är det iterable protokollet ett standardsätt för att definiera objekt som du kan iterera över, till exempel i en för av slinga.

Till exempel:

konst frukter = ["Banan", "Mango", "Äpple", "Druvor"];

för (konst iterator av frukt) {
trösta.log (iterator);
}

/*
Banan
Mango
Äpple
Vindruvor
*/

Detta exempel upprepas över frukter array med hjälp av en för av slinga. I varje iteration loggas det aktuella värdet till konsolen. Detta är möjligt eftersom arrayer är itererbara.

Vissa JavaScript-typer, som Arrays, Strings, Uppsättningar och kartor, är inbyggda iterables eftersom de (eller ett av objekten uppåt i prototypkedjan) implementerar en @@iterator metod.

Andra typer, som objekt, är inte itererbara som standard.

Till exempel:

konst iterObject = {
bilar: ["Tesla", "BMW", "Toyota"],
djur: ["Katt", "Hund", "Hamster"],
mat: ["Burgare", "Pizza", "Pasta"],
};

för (konst iterator av iterObject) {
trösta.log (iterator);
}

// TypeError: iterObject kan inte itereras

Det här exemplet visar vad som händer när du försöker iterera över ett objekt som inte är iterbart.

Göra ett objekt Iterable

För att göra ett objekt itererbart måste du implementera en Symbol.iterator metod på objektet. För att bli iterabel måste denna metod returnera ett objekt som implementerar IteratorResult gränssnitt.

De Symbol.iterator symbolen tjänar samma syfte som @@iterator och kan användas omväxlande i "specifikation" men inte i kod som @@iterator är inte giltig JavaScript-syntax.

Kodblocken nedan ger ett exempel på hur man gör ett objekt itererbart med hjälp av iterObject.

Lägg först till Symbol.iterator metod för att iterObject använder sig av en funktion deklaration.

Såhär:

iterObject[Symbol.iterator] = fungera () {
// Efterföljande kodblock kommer hit...
}

Därefter måste du komma åt alla nycklar i objektet du vill göra iterable. Du kan komma åt nycklarna med hjälp av Objekt.nycklar metod, som returnerar en uppsättning av de uppräknade egenskaperna för ett objekt. För att returnera en mängd iterObjects nycklar, passera detta nyckelord som argument för att Objekt.nycklar.

Till exempel:

låta objProperties = Objekt.keys(detta)

Åtkomst till denna array gör att du kan definiera objektets iterationsbeteende.

Därefter måste du hålla reda på objektets iterationer. Du kan uppnå detta med hjälp av räknarvariabler.

Till exempel:

låta propertyIndex = 0;
låta childIndex = 0;

Du kommer att använda den första räknarvariabeln för att hålla reda på objektegenskaperna och den andra för att hålla reda på egenskapens underordnade.

Därefter måste du implementera och returnera Nästa metod.

Såhär:

lämna tillbaka {
Nästa() {
// Efterföljande kodblock kommer hit...
}
}

Inuti Nästa metod måste du hantera ett kantfall som uppstår när hela objektet har itererats över. För att hantera kantfallet måste du returnera ett föremål med värde satt till odefinierad och Gjort satt till Sann.

Om det här fallet inte hanteras, kommer försök att iterera över objektet att resultera i en oändlig loop.

Så här hanterar du kantfodralet:

om (propertyIndex > objektEgenskaper.längd- 1) {
lämna tillbaka {
värde: odefinierad,
Gjort: Sann,
};
}

Därefter måste du komma åt objektegenskaperna och deras underordnade element med hjälp av räknarvariablerna du deklarerade tidigare.

Såhär:

// Åtkomst till överordnade och underordnade egenskaper
konst egenskaper = detta[objProperties[propertyIndex]];

konst egenskap = egenskaper[barnindex];

Därefter måste du implementera lite logik för att öka räknarvariablerna. Logiken bör återställa barnindex när inga fler element finns i en egenskaps array och flytta till nästa egenskap i objektet. Dessutom bör den öka barnindex, om det fortfarande finns element i den aktuella egenskapens array.

Till exempel:

// Indexökningslogik
if (childIndex >= egenskaper.längd - 1) {
// om det inte finns fler element i den underordnade matrisen
// återställabarnindex
childIndex = 0;

// Flytta till nästa egenskap
propertyIndex++;
} annan {
// Flytta till nästa element i den underordnade matrisen
childIndex++
}

Slutligen, returnera ett objekt med Gjort egenskapen inställd på falsk och den värde egenskapen inställd på det aktuella underordnade elementet i iterationen.

Till exempel:

lämna tillbaka {
Gjort: falsk,
värde: fastighet,
};

Du är klar Symbol.iterator funktionen bör likna kodblocket nedan:

iterObject[Symbol.iterator] = fungera () {
konst objProperties = Objekt.keys(detta);
låta propertyIndex = 0;
låta childIndex = 0;

lämna tillbaka {
Nästa: () => {
//Hanteringskantfodral
om (propertyIndex > objektEgenskaper.längd- 1) {
lämna tillbaka {
värde: odefinierad,
Gjort: Sann,
};
}

// Åtkomst till överordnade och underordnade egenskaper
konst egenskaper = detta[objProperties[propertyIndex]];

konst egenskap = egenskaper[barnindex];

// Indexökningslogik
if (childIndex >= egenskaper.längd - 1) {
// om det inte finns fler element i den underordnade matrisen
// återställabarnindex
childIndex = 0;

// Flytta till nästa egenskap
propertyIndex++;
} annan {
// Flytta till nästa element i den underordnade matrisen
childIndex++
}

lämna tillbaka {
Gjort: falsk,
värde: fastighet,
};
},
};
};

Kör a för av slinga på iterObject efter denna implementering kommer inte att ge ett fel eftersom den implementerar en Symbol.iterator metod.

Att manuellt implementera iteratorer, som vi gjorde ovan, rekommenderas inte eftersom det är mycket felbenäget och logiken kan vara svår att hantera.

JavaScript-generatorer

En JavaScript-generator är en funktion som du kan pausa och återuppta dess körning när som helst. Detta beteende gör att den kan producera en sekvens av värden över tiden.

En generatorfunktion, som är en funktion som returnerar en generator, ger ett alternativ till att skapa iteratorer.

Du kan skapa en generatorfunktion på samma sätt som du skapar en funktionsdeklaration i JavaScript. Den enda skillnaden är att du måste lägga till en asterisk (*) till funktionsnyckelordet.

Till exempel:

fungera* exempel () {
lämna tillbaka"Generator"
}

När du anropar en normal funktion i JavaScript returnerar den värdet som anges av dess lämna tillbaka nyckelord eller odefinierad annat. Men en generatorfunktion returnerar inte något värde direkt. Den returnerar ett Generator-objekt, som du kan tilldela en variabel.

För att komma åt det aktuella värdet för iteratorn, ring till Nästa metod på Generator-objektet.

Till exempel:

konst gen = exempel();

console.log (gen.next()); // { värde: 'Generator', Gjort: Sann }

I exemplet ovan är värde egendom kom från en lämna tillbaka nyckelord, vilket effektivt avslutar generatorn. Detta beteende är i allmänhet oönskat med generatorfunktioner, eftersom det som skiljer dem från normala funktioner är möjligheten att pausa och starta om körningen.

Nyckelordet för avkastning

De avkastning nyckelordet ger ett sätt att iterera genom värden i generatorer genom att pausa exekveringen av en generatorfunktion och returnera värdet som följer den.

Till exempel:

fungera* exempel() {
avkastning"Model S"
avkastning"Model X"
avkastning"Cybertruck"

lämna tillbaka"Tesla"
}

konst gen = exempel();

console.log (gen.next()); // { värde: 'Model S', Gjort: falsk }

I exemplet ovan, när Nästa metoden anropas på exempel generatorn pausar den varje gång den stöter på avkastning nyckelord. De Gjort egenskap kommer också att ställas in på falsk tills den stöter på en lämna tillbaka nyckelord.

Ringer till Nästa metod flera gånger på exempel generator för att demonstrera detta, har du följande som utdata.

console.log (gen.next()); // { värde: "Model X", Gjort: falsk }
console.log (gen.next()); // { värde: "Cybertruck", Gjort: falsk }
console.log (gen.next()); // { värde: "Tesla", Gjort: Sann }

trösta.log (gen.next()); // { värde: odefinierat, gjort: sant }

Du kan också iterera över ett Generator-objekt med hjälp av för av slinga.

Till exempel:

för (konst iterator av gen) {
trösta.log (iterator);
}

/*
Modell S
Modell X
Cyberlastbil
*/

Använda iteratorer och generatorer

Även om iteratorer och generatorer kan verka som abstrakta begrepp, är de inte det. De kan vara till hjälp när du arbetar med oändliga dataströmmar och datainsamlingar. Du kan också använda dem för att skapa unika identifierare. Statliga förvaltningsbibliotek som MobX-State-Tree (MST) använder dem också under huven.