Dacă ați fost un dezvoltator JavaScript în ultimii doi-cinci ani, veți fi întâlnit cu siguranță postări care vorbesc despre Generatori și Iteratori. Deși Generatoarele și Iteratorii sunt asociate în mod inerent, Generatoarele par un pic mai intimidante decât celelalte.
Iteratorii sunt o implementare a obiectelor Iterable, cum ar fi hărțile, array-urile și șirurile de caractere, care ne permite să iterăm peste ele folosind next(). Aceștia au o mare varietate de cazuri de utilizare în Generators, Observables și Spread operators.
Recomand următorul link pentru cei care nu cunosc iteratorii, Guide to Iterators.
Pentru a verifica dacă obiectul dvs. este conform cu protocolul iterable, verificați folosind simbolul încorporat Symbol.iterator:
Generators introdus ca parte a ES6 nu a suferit nicio modificare pentru versiunile ulterioare ale JavaScript și sunt aici pentru a rămâne mai mult timp. Adică foarte mult timp! Așadar, nu se poate fugi de ele. Deși ES7 și ES8 au unele actualizări noi, acestea nu au aceeași magnitudine de schimbare pe care ES6 a avut-o față de ES5, care a dus JavaScript la următorul nivel, ca să spunem așa.
Până la sfârșitul acestui post, sunt sigur că veți avea o înțelegere solidă a modului în care funcționează Generatoarele de funcții. Dacă sunteți un profesionist, vă rog să mă ajutați să îmbunătățesc conținutul prin adăugarea comentariilor dvs. în răspunsuri. În cazul în care aveți dificultăți în a urmări codul, am adăugat, de asemenea, explicații pentru majoritatea codului pentru a vă ajuta să înțelegeți mai bine.
Funcțiile în JavaScript, după cum știm cu toții, „rulează până la return/end”. Funcțiile Generator, pe de altă parte, „rulează până la yield/return/end”. Spre deosebire de funcțiile normale, funcțiile Generator Functions, odată apelate, returnează obiectul Generator Object, care deține întregul Generator Iterable care poate fi iterat folosind metoda next() sau for…of loop.
Care apel next() pe generator execută fiecare linie de cod până la următorul yield pe care îl întâlnește și își suspendă temporar execuția.
Sintactic sunt identificate cu un *, fie funcția* X, fie funcția *X, – ambele semnifică același lucru.
După ce sunt create, apelarea funcției generatorului returnează obiectul Generator. Acest obiect generator trebuie să fie atribuit unei variabile pentru a ține evidența metodelor next() ulterioare apelate asupra sa. Dacă generatorul nu este atribuit unei variabile, atunci el va ceda întotdeauna doar până la prima expresie yield la fiecare next().
Funcțiile generator sunt construite în mod normal folosind expresii yield. Fiecare randament din interiorul funcției generatoare este un punct de oprire înainte de începerea următorului ciclu de execuție. Fiecare ciclu de execuție este declanșat prin intermediul metodei next() a generatorului.
La fiecare apel next(), expresia yield returnează valoarea sa sub forma unui obiect care conține următorii parametri.
{ value: 10, done: false } // assuming that 10 is the value of yield
- Valoare – este tot ceea ce este scris în partea dreaptă a cuvântului cheie yield, poate fi un apel de funcție, un obiect sau practic orice. Pentru yield-uri goale, această valoare este nedefinită.
- Done – indică starea generatorului, dacă poate fi executat în continuare sau nu. Atunci când done returnează true, înseamnă că funcția și-a terminat execuția.
(Dacă vi se pare că vă depășește puțin, veți avea mai multă claritate după ce veți vedea exemplul de mai jos…)
Note: În exemplul de mai sus, funcția generatoare accesată direct, fără un înveliș, se execută întotdeauna numai până la primul randament. Prin urmare, prin definiție, trebuie să atribuiți generatorul unei variabile pentru a itera corect peste el.
Ciclul de viață al unei funcții Generator
Înainte de a continua, să aruncăm o privire rapidă asupra diagramei bloc a ciclului de viață al funcției Generator:
De fiecare dată când se întâlnește un randament, funcția generatoare returnează un obiect care conține valoarea randamentului întâlnit și starea „done”. În mod similar, atunci când se întâlnește un randament, se obține valoarea randamentului și, de asemenea, starea done ca fiind adevărată. Ori de câte ori statusul done este returnat ca fiind adevărat, înseamnă în esență că funcția generator și-a finalizat execuția și nu mai este posibil nici un alt randament.
Toate elementele de după primul return sunt ignorate, inclusiv alte expresii yield.
Citește mai departe pentru a înțelege mai bine diagrama bloc.
Asemnarea randamentului la o variabilă
În exemplul de cod anterior am văzut o introducere în crearea unui generator de bază cu un yield. Și am obținut rezultatul așteptat. Acum, să presupunem că atribuim întreaga expresie yield la o variabilă în codul de mai jos.
Care este rezultatul întregii expresii yield trecute la variabilă ? Nimic sau nedefinit …
De ce ? Începând cu a doua next(), yield-ul anterior este înlocuit cu argumentele trecute în funcția următoare. Din moment ce nu trecem nimic aici în metoda next, se presupune că întreaga „expresie de randament anterioară” este nedefinită.
Cu acest lucru în minte, să trecem la următoarea secțiune pentru a înțelege mai multe despre trecerea argumentelor în metoda next().
Pasarea argumentelor în metoda next()
Cu referire la diagrama bloc de mai sus, să vorbim despre trecerea argumentelor în funcția next. Aceasta este una dintre cele mai dificile părți ale întregii implementări a generatorului.
Să luăm în considerare următoarea bucată de cod, în care randamentul este atribuit unei variabile, dar de data aceasta trecem o valoare în metoda next().
Să ne uităm la codul de mai jos în consolă. Și explicația imediat după aceea.
Explicație:
- Când apelăm prima metodă next(20), fiecare linie de cod până la primul yield este tipărită. Deoarece nu avem nicio expresie yield anterioară, această valoare 20 este eliminată. În ieșire obținem valoarea yield ca i*10, care este 100 aici. De asemenea, stadiul de execuție se oprește la primul randament și const j nu este încă setat.
- Cel de-al doilea apel next(10) înlocuiește întreaga expresie yield cu 10, imaginați-vă yield (i * 10) = 10, care continuă să seteze valoarea const j la 50 înainte de a returna valoarea celui de-al doilea randament. Valoarea yield aici este 2 * 50 / 4 = 25.
- Al treilea next(5), înlocuiește întreaga a doua expresie yield cu 5, aducând valoarea lui k la 5. Și în continuare continuă să execute instrucțiunea return și returnează (x + y + z) => (10 + 50 + 5) = 65 ca valoare finală a randamentului, împreună cu done true.
Acest lucru ar putea fi puțin copleșitor pentru cei care citesc pentru prima dată, dar acordați-vă 5 minute bune pentru a-l citi din nou și din nou pentru a înțelege complet.
Pasarea lui Yield ca argument al unei funcții
Există un număr n de cazuri de utilizare în jurul lui yield în ceea ce privește modul în care poate fi folosit în interiorul unui generator de funcții. Să ne uităm la codul de mai jos pentru o astfel de utilizare interesantă a yield, împreună cu explicația.
Explicație
- Primul next() produce o valoare nedefinită deoarece expresia yield nu are valoare.
- Cel de-al doilea next() produce „I am useless”, valoarea care a fost transmisă. Și pregătește argumentul pentru apelarea funcției.
- Cel de-al treilea next(), apelează funcția cu un argument nedefinit. După cum s-a menționat mai sus, metoda next() apelată fără niciun argument înseamnă, în esență, că întreaga expresie yield anterioară este nedefinită. Prin urmare, aceasta tipărește undefined și termină execuția.
Yield cu un apel de funcție
Pe lângă returnarea valorilor, yield poate, de asemenea, să apeleze funcții și să returneze valoarea sau să o tipărească. Să ne uităm la codul de mai jos și să înțelegem mai bine.
Aici, în cod, yield* iterază peste fiecare obiect iterabil posibil care este trecut ca expresie a sa. Cred că codul în sine se explică de la sine.
Bune practici
Pe lângă toate acestea, fiecare iterator/generator poate fi iterat peste o buclă for…of. Similar metodei noastre next(), care este apelată în mod explicit, bucla for…of trece în mod intern la următoarea iterație pe baza cuvântului cheie yield. Și itera doar până la ultimul yield și nu procesează declarațiile de returnare ca metoda next().
Puteți verifica același lucru în codul de mai jos.
Valoarea finală de întoarcere nu este tipărită, deoarece bucla for…of iterază numai până la ultimul yield. Așadar, intră în categoria celor mai bune practici evitarea declarațiilor return în interiorul unei funcții generatoare, deoarece aceasta ar afecta capacitatea de reutilizare a funcției atunci când este iterată printr-o buclă for…of.
Concluzie
Sper că acest lucru acoperă cazurile de utilizare de bază ale funcțiilor generatoare și sper sincer că a oferit o mai bună înțelegere a modului în care funcționează generatoarele în JavaScript ES6 și superior. Dacă vă place conținutul meu, vă rog să lăsați 1, 2, 3 sau chiar 50 de aplauze :).
Vă rog să mă urmăriți pe contul meu GitHub pentru mai multe proiecte JavaScript și Full-Stack:
.