Itérables et itérateurs JavaScript
Comprendre les itérables est fondamental en JavaScript. Ils permettent un accès séquentiel aux éléments et s'intègrent aux boucles, spread et destructuration.
Introduction aux itérables JavaScript
Les itérables JavaScript sont des objets qui implémentent un protocole spécifique, leur permettant d'être consommés par des constructions d'itération telles que for...of. Ce guide couvre les concepts fondamentaux, les itérables intégrés et les techniques pratiques pour travailler avec des collections.
Que sont les itérables en JavaScript ?
Dans sa forme la plus simple, un itérable est un object qui implémente la méthode Symbol.iterator, permettant un accès séquentiel à ses éléments. Plusieurs types intégrés en JavaScript sont itérables, notamment Array, String, Map, Set et bien d'autres. Il est important de distinguer un itérable (l'object parcouru) d'un itérateur (l'object renvoyé par Symbol.iterator qui effectue réellement le parcours). Ces itérables sont essentiels à diverses opérations, comme la boucle et la manipulation de données.
Pour une exploration détaillée des objets JavaScript Map et Set, veuillez consulter notre guide complet sur JavaScript Map et Set.
Exemple d'itérable : Array
Voyons un exemple basique d'itérable en JavaScript :
Cet extrait de code illustre l'itération sur un array de fruits, un itérable courant. La boucle for...of appelle automatiquement la méthode Symbol.iterator de l'itérable et consomme l'itérateur résultant jusqu'à ce que done soit true. Voir les boucles JavaScript pour la famille complète des constructions de boucle.
Ne confondez pas for...of avec for...in. for...of itère sur les valeurs produites par un itérable (éléments d'array, caractères de string, entrées de Map). for...in itère sur les clés de propriété énumérables d'un object — y compris les héritées — et est destiné aux objets simples, pas aux arrays. Utiliser for...in sur un array donne des strings d'index ("0", "1", …) et peut récupérer des propriétés supplémentaires, donc préférez for...of pour les données ordonnées.
Le protocole itérateur
La pierre angulaire d'un itérable est sa méthode Symbol.iterator. Le protocole est un contrat précis :
- Un itérable possède une méthode identifiée par
Symbol.iterator. L'appeler renvoie un itérateur. - Un itérateur est un object avec une méthode
next(). - Chaque appel à
next()renvoie un object{ value, done }:done: false—valueest l'élément suivant dans la séquence.done: true— l'itération est terminée (valueest alors ignorée, ou porte un résultat final optionnel).
Tout object qui respecte ce contrat fonctionne avec for...of, l'opérateur spread, la déstructuration et Array.from() — même si vous avez écrit l'object vous-même. Symbol est une clé unique intégrée ; voir le type Symbol pour comprendre pourquoi ces méthodes de protocole en utilisent une plutôt qu'un nom de string ordinaire.
Exemple : un itérable range personnalisé
Un cas d'usage classique est un object qui produit une plage numérique de manière paresseuse, sans jamais construire un array :
Dans cet exemple, la méthode [Symbol.iterator]() renvoie un nouvel itérateur à chaque fois, de sorte que le même object range peut être parcouru plusieurs fois. La syntaxe abrégée de méthode garantit que this fait bien référence à l'object range. (Utiliser une fonction fléchée pour [Symbol.iterator] capturerait this de manière lexicale et casserait le schéma.)
Les générateurs : la façon simple de créer des itérables
Écrire next() et gérer l'état manuellement est verbeux. Une fonction génératrice — déclarée avec function* et utilisant yield — produit automatiquement un itérateur. Chaque yield met la fonction en pause et transmet une valeur au consommateur ; l'exécution reprend au prochain appel à next(). Le même range devient beaucoup plus concis :
Le * avant le nom de la méthode en fait une méthode génératrice, donc range est itérable avec presque aucun code répétitif. Pour tout ce que les générateurs peuvent faire — y compris la communication bidirectionnelle et la délégation avec yield* — voir les générateurs et les itérateurs et générateurs asynchrones.
Séquences infinies et paresseuses
Étant donné qu'un itérateur ne calcule la valeur suivante qu'à la demande, il peut décrire des séquences trop grandes — ou même infinies — pour tenir en mémoire. C'est la principale raison d'écrire un itérateur personnalisé plutôt que d'utiliser simplement un array :
N'utilisez jamais le spread ([...naturals()]) ni for...of sans break sur un itérable infini — il bouclera indéfiniment. Récupérez un nombre fini de valeurs avec next() à la place.
Consommer des itérables
Une fois qu'un object est itérable, tout le langage lui est ouvert : toute construction qui accepte un itérable fonctionne avec votre type personnalisé de la même façon qu'avec les arrays.
Utiliser Array.from()
La méthode Array.from() crée un nouvel array à partir de n'importe quel itérable (ou object de type array). Elle accepte également une fonction de mappage optionnelle comme second argument, appliquée à chaque élément lors de la construction de l'array — plus pratique que Array.from(it).map(fn) car cela évite une seconde passe :
Voir JavaScript Map et Set pour en savoir plus sur Set, et les méthodes d'array pour ce que vous pouvez faire une fois que vous avez un array.
La syntaxe spread avec les itérables
La syntaxe spread (...) développe un itérable partout où des arguments ou des éléments sont attendus — pour fusionner des arrays, les copier ou passer des éléments comme arguments de fonction :
Pour une vue d'ensemble complète de ... dans les positions spread et collect, voir les paramètres rest et la syntaxe spread.
Déstructuration et rest
La déstructuration extrait des valeurs de n'importe quel itérable par position, et le schéma rest (...) rassemble ce qui reste dans un array :
En savoir plus dans la déstructuration.
L'itérable string et la sécurité Unicode
Les strings sont itérables, et l'itérateur parcourt de manière cruciale les points de code Unicode, et non les unités de code 16 bits. Cela signifie que les caractères en paires de substitution (emoji, certains scripts) sont conservés intacts — contrairement à l'indexation avec [i] ou aux anciennes boucles for sur .length, qui peuvent les diviser :
Chaque fois que vous devez compter ou diviser des caractères visibles par l'utilisateur correctement, itérez la string (ou appliquez-y le spread) plutôt que de vous fier à .length. Voir le chapitre Strings pour en savoir plus.
Récapitulatif
- Un object est itérable quand il possède une méthode
[Symbol.iterator]()qui renvoie un itérateur — un object dontnext()produit{ value, done }. - Préférez un générateur (
function*/yield) plutôt que d'écrirenext()manuellement ; c'est la façon la plus courte et la moins sujette aux erreurs de rendre quelque chose d'itérable. - Utilisez
for...ofpour les valeurs de données ordonnées/itérables ; utilisezfor...inuniquement pour les clés des objets simples. - Une fois itérable, votre type s'intègre gratuitement dans spread, déstructuration, rest,
Array.from(it, mapFn)et de nombreuses API intégrées. - Les itérateurs sont paresseux, ils peuvent donc modéliser des séquences infinies ou très volumineuses qu'un array ne pourrait jamais gérer.
- Itérez les strings (ne les indexez pas) pour gérer les caractères Unicode comme les emoji en toute sécurité.