Iteracja po zbiorach danych przy użyciu tradycyjnych pętli może szybko stać się uciążliwa i powolna, zwłaszcza w przypadku ogromnych ilości danych.
Generatory i iteratory JavaScript zapewniają rozwiązanie do wydajnej iteracji dużych kolekcji danych. Korzystając z nich, możesz kontrolować przebieg iteracji, uzyskiwać wartości pojedynczo oraz wstrzymywać i wznawiać proces iteracji.
Tutaj omówisz podstawy i elementy wewnętrzne iteratora JavaScript oraz sposób generowania iteratora ręcznie i za pomocą generatora.
Iteratory JavaScript
Iterator to obiekt JavaScript, który implementuje protokół iteratora. Obiekty te robią to, mając a Następny metoda. Ta metoda zwraca obiekt, który implementuje metodę Wynik iteratora interfejs.
The Wynik iteratora interfejs składa się z dwóch właściwości: zrobione I wartość. The zrobione właściwość jest wartością logiczną, która zwraca
FAŁSZ jeśli iterator może wygenerować następną wartość w swojej sekwencji lub PRAWDA jeśli iterator zakończył swoją sekwencję.The wartość właściwość jest wartością JavaScript zwracaną przez iterator podczas jego sekwencji. Kiedy iterator zakończy swoją sekwencję (kiedy zrobionePRAWDA), ta właściwość zwraca nieokreślony.
Jak sama nazwa wskazuje, iteratory umożliwiają „iterację” obiektów JavaScript, takich jak tablice lub mapy. Takie zachowanie jest możliwe dzięki iterowalnemu protokołowi.
W JavaScript protokół iterowalny jest standardowym sposobem definiowania obiektów, które można iterować, na przykład w a dla...z pętla.
Na przykład:
konst owoce = ["Banan", "Mango", "Jabłko", "Winogrona"];
Do (konst iterator z owoce) {
konsola.log (iterator);
}
/*
Banan
Mango
Jabłko
Winogrona
*/
Ten przykład iteruje po owoce tablica za pomocą a dla...z pętla. W każdej iteracji rejestruje bieżącą wartość w konsoli. Jest to możliwe, ponieważ tablice są iterowalne.
Niektóre typy JavaScript, takie jak tablice, łańcuchy, Zestawy i mapy, są wbudowanymi iteracjami, ponieważ (lub jeden z obiektów w ich łańcuchu prototypów) implementują @@iterator metoda.
Inne typy, takie jak obiekty, domyślnie nie są iterowalne.
Na przykład:
konst iterObject = {
samochody: [„Tesli”, "BMW", „Toyota”],
Zwierząt: ["Kot", "Pies", "Chomik"],
żywność: [„Burgery”, "Pizza", "Makaron"],
};Do (konst iterator z iterObject) {
konsola.log (iterator);
}
// TypeError: iterObject nie jest iterowalny
Ten przykład pokazuje, co się dzieje, gdy próbujesz wykonać iterację po obiekcie, który nie jest iterowalny.
Tworzenie obiektu iterowalnego
Aby obiekt był iterowalny, musisz zaimplementować a Symbol.iterator metoda na obiekcie. Aby stać się iterowalnym, ta metoda musi zwrócić obiekt, który implementuje metodę Wynik iteratora interfejs.
The Symbol.iterator symbol służy temu samemu celowi co @@iterator i mogą być używane zamiennie w „specyfikacji”, ale nie w kodzie jako @@iterator nie jest poprawną składnią JavaScript.
Poniższe bloki kodu zawierają przykład, jak sprawić, by obiekt był iterowalny przy użyciu metody iterObject.
Najpierw dodaj tzw Symbol.iterator sposób na iterObject za pomocą funkcja deklaracja.
jak tak:
iterObiekt[Symbol.iterator] = funkcjonować () {
// Kolejne bloki kodu przechodzą tutaj...
}
Następnie musisz uzyskać dostęp do wszystkich kluczy w obiekcie, który chcesz uczynić iterowalnym. Możesz uzyskać dostęp do kluczy za pomocą klucze obiektów metoda, która zwraca tablicę wyliczalnych właściwości obiektu. Aby zwrócić tablicę iterObjectklucze, podaj Ten słowo kluczowe jako argument do klucze obiektów.
Na przykład:
pozwalać właściwości obiektu = Obiekt.Klucze(Ten)
Dostęp do tej tablicy pozwoli na zdefiniowanie zachowania iteracyjnego obiektu.
Następnie musisz śledzić iteracje obiektu. Można to osiągnąć za pomocą zmiennych liczników.
Na przykład:
pozwalać Indeks właściwości = 0;
pozwalać indeks potomny = 0;
Użyjesz pierwszej zmiennej licznika do śledzenia właściwości obiektu, a drugiej do śledzenia dzieci właściwości.
Następnie musisz zaimplementować i zwrócić plik Następny metoda.
jak tak:
powrót {
Następny() {
// Kolejne bloki kodu przechodzą tutaj...
}
}
W środku Następny metody, będziesz musiał obsłużyć przypadek Edge, który występuje, gdy cały obiekt został powtórzony. Aby obsłużyć przypadek Edge, musisz zwrócić obiekt z wartość Ustawić nieokreślony I zrobione Ustawić PRAWDA.
Jeśli ten przypadek nie zostanie obsłużony, próba iteracji po obiekcie spowoduje powstanie nieskończonej pętli.
Oto jak postępować w przypadku Edge:
Jeśli (Indeks właściwości > obiektWłaściwości.długość- 1) {
powrót {
wartość: nieokreślony,
zrobione: PRAWDA,
};
}
Następnie musisz uzyskać dostęp do właściwości obiektu i ich elementów podrzędnych za pomocą zadeklarowanych wcześniej zmiennych liczników.
jak tak:
// Dostęp do właściwości nadrzędnych i podrzędnych
konst właściwości = Ten[obiektWłaściwości[wskaźnikwłaściwości]];
konst właściwość = właściwości [indeks podrzędny];
Następnie musisz zaimplementować logikę zwiększania zmiennych licznika. Logika powinna zresetować dzieckoIndeks gdy w tablicy właściwości nie ma więcej elementów i przechodzi do następnej właściwości w obiekcie. Dodatkowo powinien rosnąć dzieckoIndeks, jeśli w tablicy bieżącej właściwości nadal znajdują się elementy.
Na przykład:
// Logika inkrementacji indeksu
if (childIndex >= właściwości. długość - 1) {
// jeśli w tablicy potomnej nie ma więcej elementów
// Resetowaniedzieckoindeks
indeks potomny = 0;
// Przejdź do następnej właściwości
Indekswłaściwości++;
} w przeciwnym razie {
// Przejdź do następnego elementu w tablicy potomnej
dzieckoIndeks++
}
Na koniec zwróć obiekt z zrobione właściwość ustawiona na FAŁSZ i wartość właściwość ustawiona na bieżący element podrzędny w iteracji.
Na przykład:
powrót {
zrobione: FAŁSZ,
wartość: nieruchomość,
};
Twój ukończony Symbol.iterator funkcja powinna być podobna do poniższego bloku kodu:
iterObiekt[Symbol.iterator] = funkcjonować () {
konst właściwości obiektu = Obiekt.Klucze(Ten);
pozwalać Indeks właściwości = 0;
pozwalać indeks potomny = 0;powrót {
Następny: () => {
//Obsługa przypadków krawędziowych
Jeśli (Indeks właściwości > obiektWłaściwości.długość- 1) {
powrót {
wartość: nieokreślony,
zrobione: PRAWDA,
};
}// Dostęp do właściwości nadrzędnych i podrzędnych
konst właściwości = Ten[obiektWłaściwości[wskaźnikwłaściwości]];
konst właściwość = właściwości [indeks podrzędny];// Logika inkrementacji indeksu
if (childIndex >= właściwości. długość - 1) {
// jeśli w tablicy potomnej nie ma więcej elementów
// Resetowaniedzieckoindeks
indeks potomny = 0;
// Przejdź do następnej właściwości
Indekswłaściwości++;
} w przeciwnym razie {
// Przejdź do następnego elementu w tablicy potomnej
dzieckoIndeks++
}
powrót {
zrobione: FAŁSZ,
wartość: nieruchomość,
};
},
};
};
Bieganie dla...z zapętlić iterObject po tej implementacji nie zgłosi błędu, ponieważ implementuje a Symbol.iterator metoda.
Ręczne wdrażanie iteratorów, jak to zrobiliśmy powyżej, nie jest zalecane, ponieważ jest bardzo podatne na błędy, a logika może być trudna do zarządzania.
Generatory JavaScript
Generator JavaScript to funkcja, której działanie możesz wstrzymać i wznowić w dowolnym momencie. Takie zachowanie pozwala na tworzenie sekwencji wartości w czasie.
Funkcja generatora, która jest funkcją zwracającą generator, stanowi alternatywę dla tworzenia iteratorów.
Możesz utworzyć funkcję generatora w ten sam sposób, w jaki tworzysz deklarację funkcji w JavaScript. Jedyna różnica polega na tym, że musisz dołączyć gwiazdkę (*) do słowa kluczowego funkcji.
Na przykład:
funkcjonować* przykład () {
powrót"Generator"
}
Kiedy wywołujesz normalną funkcję w JavaScript, zwraca ona wartość określoną przez jej powrót słowo kluczowe lub nieokreślony W przeciwnym razie. Ale funkcja generatora nie zwraca natychmiast żadnej wartości. Zwraca obiekt Generatora, który można przypisać do zmiennej.
Aby uzyskać dostęp do bieżącej wartości iteratora, wywołaj metodę Następny metoda na obiekcie Generatora.
Na przykład:
konst gen = przykład();
console.log (gen.next()); // { wartość: 'Generator', zrobione: PRAWDA }
W powyższym przykładzie wartość majątek pochodził z A powrót słowo kluczowe, skutecznie kończąc generator. Takie zachowanie jest ogólnie niepożądane w przypadku funkcji generatora, ponieważ tym, co odróżnia je od normalnych funkcji, jest możliwość wstrzymywania i ponownego uruchamiania wykonywania.
Wydajność Słowo kluczowe
The dawać słowo kluczowe umożliwia iterację wartości w generatorach poprzez wstrzymywanie wykonywania funkcji generatora i zwracanie następującej po nim wartości.
Na przykład:
funkcjonować* przykład() {
dawać„Model S”
dawać„Model X”
dawać„Cyber ciężarówka”powrót„Tesli”
}konst gen = przykład();
console.log (gen.next()); // { wartość: „Model S”, zrobione: FAŁSZ }
W powyższym przykładzie, gdy plik Następny metoda zostaje wywołana na przykład generator, zatrzyma się za każdym razem, gdy napotka dawać słowo kluczowe. The zrobione właściwość zostanie również ustawiona na FAŁSZ aż napotka A powrót słowo kluczowe.
Dzwoniąc do Następny metodę wielokrotnie na przykład generator, aby to zademonstrować, otrzymasz następujące dane wyjściowe.
console.log (gen.next()); // { wartość: „Model X”, zrobione: FAŁSZ }
console.log (gen.next()); // { wartość: „Cyber ciężarówka”, zrobione: FAŁSZ }
console.log (gen.next()); // { wartość: „Tesla”, zrobione: PRAWDA }
konsola.log (gen.next()); // {wartość: niezdefiniowana, zrobione: prawda}
Możesz także iterować po obiekcie Generatora, używając metody dla...z pętla.
Na przykład:
Do (konst iterator z gen) {
konsola.log (iterator);
}
/*
Model S
Model X
Cyber ciężarówka
*/
Korzystanie z iteratorów i generatorów
Chociaż iteratory i generatory mogą wydawać się pojęciami abstrakcyjnymi, tak nie jest. Mogą być pomocne podczas pracy z nieskończonymi strumieniami danych i zbiorami danych. Można ich również używać do tworzenia unikalnych identyfikatorów. Biblioteki zarządzania stanem, takie jak MobX-State-Tree (MST), również używają ich pod maską.