Utilisation d'itérateurs et de générateurs en JavaScript pour optimiser le code

introduction

Avez-vous déjà eu besoin de parcourir une liste en boucle, mais l'opération a pris beaucoup de temps? Avez-vous déjà eu un blocage du programme parce qu'une opération utilisait trop de mémoire? Cela m'est arrivé lorsque j'ai essayé d'implémenter une fonction qui génère des nombres premiers. 

Générer des nombres premiers jusqu'à un million a pris beaucoup plus de temps que je l'aurais souhaité. Mais générer des nombres allant jusqu'à 10 millions était impossible. Mon programme se bloquerait ou se bloquerait tout simplement. J'utilisais déjà le tamis d'Eratosthenes, qui est supposé être plus efficace pour générer des nombres premiers que l'approche par force brute.

Si vous vous trouvez dans une situation similaire, vous pouvez utiliser un algorithme différent. Il existe des algorithmes de recherche et de tri qui fonctionnent mieux avec des entrées plus volumineuses. L'inconvénient est que ces algorithmes peuvent être plus difficiles à comprendre tout de suite. Une autre option consiste à utiliser un langage de programmation différent. 

Un langage compilé peut être capable de traiter le code beaucoup plus rapidement. Mais utiliser une autre langue peut ne pas être pratique. Vous pouvez également essayer d'utiliser plusieurs threads. Encore une fois, cela peut ne pas être pratique car votre langage de programmation devrait supporter cette.

Heureusement, avec JavaScript, il existe une autre option. Si vous avez une tâche de calcul intensif, vous pouvez utiliser des itérateurs et des générateurs pour gagner en efficacité. Les itérateurs sont la propriété de certaines collections JavaScript. 

Les itérateurs améliorent l'efficacité en vous permettant de consommer les éléments d'une liste à la fois, comme s'il s'agissait d'un flux. Les générateurs sont un type de fonction spécial qui peut suspendre l'exécution. L'appel d'un générateur vous permet de produire des données un morceau à la fois sans qu'il soit nécessaire de les stocker dans une liste au préalable..

Itérateurs

Tout d’abord, passons en revue les différentes manières dont vous pouvez parcourir les collections en JavaScript. Une boucle de la forme pour (initial; condition; step) … exécute les commandes dans son corps un nombre spécifique de fois. De même, une boucle while exécutera les commandes dans son corps tant que sa condition est vraie.. 

Vous pouvez utiliser ces boucles pour parcourir une liste en incrémentant une variable d'index à chaque itération. Une itération est une exécution du corps d'une boucle. Ces boucles ne connaissent pas la structure de votre liste. Ils agissent comme des compteurs.

UNE pour / dans boucle et un pour / de Les boucles sont conçues pour parcourir des structures de données spécifiques. En parcourant une structure de données, vous parcourez chacun de ses éléments. UNE pour / dans La boucle itère sur les clés dans un objet JavaScript simple. UNE pour / de La boucle itère sur les valeurs d'un itérable. Qu'est-ce qu'un itérable? En termes simples, un itérable est un objet qui a un itérateur. Des exemples de iterables sont des tableaux et des ensembles. L'itérateur est une propriété de l'objet qui fournit un mécanisme pour traverser l'objet..

Ce qui rend un itérateur spécial est la façon dont il parcourt une collection. Les autres boucles doivent charger la totalité de la collection à l’avance pour pouvoir la parcourir, alors qu’un itérateur n’a besoin que de connaître la position actuelle dans la collection.. 

Vous accédez à l'élément en cours en appelant la méthode suivante de l'itérateur. La méthode suivante renverra la valeur de l'élément actuel et un booléen pour indiquer que vous avez atteint la fin de la collection. Voici un exemple de création d'un itérateur à partir d'un tableau.

const alpha = ['a', 'b', 'c']; const it = alpha [Symbol.iterator] (); it.next (); // valeur: 'a', done: false it.next (); // valeur: 'b', done: false it.next (); // valeur: 'c', done: false it.next (); // valeur: undefined, done: true

Vous pouvez également parcourir les valeurs de l'itérateur à l'aide d'un pour / de boucle. Utilisez cette méthode lorsque vous savez que vous souhaitez accéder à tous les éléments de l'objet. Voici comment utiliser une boucle pour parcourir la liste précédente:

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

Pourquoi voudriez-vous utiliser un itérateur? L'utilisation d'un itérateur est bénéfique lorsque le coût de traitement d'une liste est élevé. Si vous avez une source de données très volumineuse, des problèmes peuvent survenir dans votre programme si vous essayez de le parcourir, car toute la collection doit être chargée.. 

Avec un itérateur, vous pouvez charger les données en morceaux. Ceci est plus efficace car vous ne manipulez que la partie de la liste dont vous avez besoin, sans que cela entraîne des coûts supplémentaires liés au traitement de la totalité de la liste.. 

Un exemple pourrait être que vous avez chargé des données à partir d'un fichier ou d'une base de données et que vous souhaitez afficher progressivement les informations à l'écran. Vous pouvez créer un itérateur à partir des données et configurer un gestionnaire d'événements pour extraire quelques éléments chaque fois que l'événement se produit. Voici un exemple de ce à quoi une telle implémentation pourrait ressembler:

laisser les messages = charger (url); let it = posts [Symbol.iterator] (); fonction loadPosts (iterable, count) for (let i = 0; i < count; i++)  display(iterable.next().value);   document.getElementById('btnNext').onclick = loadPosts(it, 5);

Générateurs

Si vous souhaitez créer une collection, vous pouvez le faire avec un générateur. Une fonction génératrice peut renvoyer des valeurs une à la fois en suspendant l'exécution à chaque itération. Lorsque vous créez une instance d'un générateur, vous pouvez accéder à ces éléments à l'aide d'un itérateur. Voici la syntaxe générale pour créer une fonction génératrice:

function * genFunc () … valeur de rendement; 

le * signifie qu'il s'agit d'une fonction génératrice. le rendement mot-clé met notre fonction en pause et fournit l'état du générateur à ce moment particulier. Pourquoi voudriez-vous utiliser un générateur? Vous utiliseriez un générateur lorsque vous souhaitez générer de manière algorithmique une valeur dans une collection. C'est particulièrement utile si vous avez une collection très grande ou infinie. Regardons un exemple pour comprendre comment cela nous aide. 

Supposons que vous ayez créé un jeu de billard en ligne et que vous souhaitiez faire correspondre les joueurs aux salles de jeu. Votre objectif est de générer toutes les manières dont vous pouvez choisir deux joueurs distincts dans votre liste de 2 000 joueurs. Les combinaisons à deux joueurs générées à partir de la liste ['a B c d'] serait ab, ac, ad, bc, bd, cd.  C'est une solution utilisant des boucles imbriquées:

combos de fonctions (liste) const n = list.length; let result = []; pour (soit 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']));

Maintenant, essayez d’exécuter la fonction avec une liste de 2 000 éléments. (Vous pouvez initialiser votre liste en utilisant une boucle for qui ajoute les nombres de 1 à 2 000 à un tableau). Qu'est-ce qui se passe maintenant lorsque vous exécutez votre code? 

Lorsque je lance le code dans un éditeur en ligne, la page Web se bloque. Lorsque je l'essaie dans la console sous Chrome, je peux voir la sortie s'imprimer lentement. Cependant, le processeur de mon ordinateur commence à fonctionner de manière excessive et je dois forcer l'arrêt de Chrome. Voici le code révisé utilisant une fonction génératrice:

fonction * combos (liste) const n = list.length; pour (soit 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(); 

Un autre exemple est si nous voulions générer les nombres dans la séquence de Fibonacci jusqu'à l'infini. Voici une implémentation:

fonction * fibGen () let current = 0; laissez suivant = 1; while (true) rendement actuel; laissez nextNum = current + next; current = next; next = nextNum;  let it = fibGen (); it.next (). value; // 0 it.next (). Value; // 1 it.next (). Value; // 1 it.next (). Value; // 2

Normalement, une boucle infinie plantera votre programme. le fibgen function est capable de fonctionner à l'infini car il n'y a pas de condition d'arrêt. Mais puisqu'il s'agit d'un générateur, vous contrôlez le moment auquel chaque étape est exécutée..

La revue

Les itérateurs et les générateurs sont pratiques lorsque vous souhaitez traiter une collection de manière incrémentielle. Vous gagnez en efficacité en gardant trace de l'état de la collection au lieu de tous les éléments de la collection. Les articles de la collection sont évalués un par un et l'évaluation du reste de la collection est reportée à plus tard.. 

Les itérateurs constituent un moyen efficace de parcourir et de manipuler des listes volumineuses. Les générateurs constituent un moyen efficace de créer des listes. Vous devriez essayer ces techniques lorsque vous utiliseriez autrement un algorithme complexe ou implémentez une programmation parallèle pour optimiser votre code..

Si vous recherchez des ressources supplémentaires à étudier ou à utiliser dans votre travail, consultez ce que nous avons à votre disposition sur le marché Envato..

Apprendre JavaScript: le guide complet

Nous avons créé un guide complet pour vous aider à apprendre le JavaScript, que vous soyez débutant en tant que développeur Web ou que vous souhaitiez explorer des sujets plus avancés..

Ressources

  • Modèles de conception: Éléments d'un logiciel orienté objet réutilisable: Modèle d'Iterator
  • Spécification ECMAScript pour les itérateurs et les générateurs
  • Structure et interprétation des programmes d'ordinateur - Chapitre 3.5: Les flux