Les objets itérables sont une généralisation des tableaux. C'est un concept qui nous permet de rendre n'importe quel objet utilisable dans une boucle for..of
.
Bien sûr, les tableaux sont itérables. Mais il existe de nombreux autres objets intégrés, qui sont également itérables. Par exemple, les chaînes sont également itérables.
Si un objet n'est pas techniquement un tableau, mais représente une collection (liste, ensemble) de quelque chose, alors for..of
est une excellente syntaxe pour le parcourir, voyons donc comment le faire fonctionner.
Nous pouvons facilement saisir le concept d'itérables en créant l'un des nôtres.
Par exemple, nous avons un objet qui n'est pas un tableau, mais qui semble approprié pour for..of
.
Comme un objet range
qui représente un intervalle de nombres :
soit plage = { de : 1, à: 5 } ; // Nous voulons que le for..of fonctionne : // pour (soit le nombre de plage) ... num=1,2,3,4,5
Pour rendre l'objet range
itérable (et ainsi permettre for..of
travail), nous devons ajouter une méthode à l'objet nommée Symbol.iterator
(un symbole intégré spécial juste pour cela).
Lorsque for..of
démarre, il appelle cette méthode une fois (ou des erreurs si elle n'est pas trouvée). La méthode doit renvoyer un itérateur – un objet avec la méthode next
.
Ensuite, for..of
ne fonctionne qu'avec cet objet renvoyé .
Lorsque for..of
veut la valeur suivante, il appelle next()
sur cet objet.
Le résultat de next()
doit avoir la forme {done: Boolean, value: any}
, où done=true
signifie que la boucle est terminée, sinon value
est la valeur suivante.
Voici l'implémentation complète de range
avec des remarques :
soit plage = { de : 1, à: 5 } ; // 1. appel à for..of appelle initialement ceci range[Symbol.iterator] = fonction() { // ...il renvoie l'objet itérateur : // 2. En avant, for..of fonctionne uniquement avec l'objet itérateur ci-dessous, lui demandant les valeurs suivantes retour { actuel : this.from, dernier : ceci.à, // 3. next() est appelé à chaque itération par la boucle for..of suivant() { // 4. il doit renvoyer la valeur sous forme d'objet {done:.., value :...} si (this.current <= this.last) { return { done : false, valeur : this.current++ } ; } autre { return { done: vrai } ; } } } ; } ; // maintenant ça marche ! pour (soit le nombre de plage) { alerte(num); // 1, puis 2, 3, 4, 5 }
Veuillez noter la caractéristique principale des itérables : la séparation des préoccupations.
La range
elle-même n'a pas la méthode next()
.
Au lieu de cela, un autre objet, appelé « itérateur », est créé par l'appel à range[Symbol.iterator]()
, et son next()
génère des valeurs pour l'itération.
Ainsi, l’objet itérateur est distinct de l’objet sur lequel il itère.
Techniquement, nous pouvons les fusionner et utiliser range
lui-même comme itérateur pour simplifier le code.
Comme ça:
soit plage = { de : 1, à: 5, [Symbol.iterator]() { this.current = this.from; rends ceci ; }, suivant() { si (this.current <= this.to) { return { done : false, valeur : this.current++ } ; } autre { return { done: vrai } ; } } } ; pour (soit le nombre de plage) { alerte(num); // 1, puis 2, 3, 4, 5 }
Maintenant, range[Symbol.iterator]()
renvoie l'objet range
lui-même : il possède la méthode next()
nécessaire et se souvient de la progression de l'itération actuelle dans this.current
. Plus court ? Oui. Et parfois, c'est bien aussi.
L'inconvénient est qu'il est désormais impossible d'avoir deux boucles for..of
s'exécutant simultanément sur l'objet : elles partageront l'état d'itération, car il n'y a qu'un seul itérateur : l'objet lui-même. Mais deux for-ofs parallèles sont une chose rare, même dans des scénarios asynchrones.
Itérateurs infinis
Des itérateurs infinis sont également possibles. Par exemple, la range
devient infinie pour range.to = Infinity
. Ou nous pouvons créer un objet itérable qui génère une séquence infinie de nombres pseudo-aléatoires. Peut également être utile.
Il n'y a aucune limitation sur next
, il peut renvoyer de plus en plus de valeurs, c'est normal.
Bien sûr, la boucle for..of
sur un tel itérable serait sans fin. Mais nous pouvons toujours l'arrêter en utilisant break
.
Les tableaux et les chaînes sont les itérables intégrés les plus largement utilisés.
Pour une chaîne, for..of
boucle sur ses caractères :
for (laisser char de "test") { // se déclenche 4 fois : une fois pour chaque caractère alerte( char ); // t, puis e, puis s, puis t }
Et cela fonctionne correctement avec les paires de substitution !
laissez str = '??'; pour (laisser char de str) { alerte( char ); // ?, et puis ? }
Pour une compréhension plus approfondie, voyons comment utiliser explicitement un itérateur.
Nous allons parcourir une chaîne exactement de la même manière que for..of
, mais avec des appels directs. Ce code crée un itérateur de chaîne et en récupère les valeurs « manuellement » :
let str = "Bonjour"; // fait la même chose que // for (let char of str) alert(char); let itérateur = str[Symbol.iterator](); tandis que (vrai) { let result = itérateur.next(); if (result.done) pause ; alerte(result.value); // affiche les caractères un par un }
Cela est rarement nécessaire, mais nous donne plus de contrôle sur le processus que for..of
. Par exemple, nous pouvons diviser le processus d'itération : itérer un peu, puis arrêter, faire autre chose, puis reprendre plus tard.
Deux termes officiels se ressemblent, mais sont très différents. Assurez-vous de bien les comprendre pour éviter toute confusion.
Les itérables sont des objets qui implémentent la méthode Symbol.iterator
, comme décrit ci-dessus.
Les tableaux sont des objets qui ont des index et length
, ils ressemblent donc à des tableaux.
Lorsque nous utilisons JavaScript pour des tâches pratiques dans un navigateur ou tout autre environnement, nous pouvons rencontrer des objets itérables ou de type tableau, ou les deux.
Par exemple, les chaînes sont à la fois itérables ( for..of
travaux sur elles) et de type tableau (elles ont des index numériques et length
).
Mais un itérable peut ne pas ressembler à un tableau. Et vice versa, un tableau de type peut ne pas être itérable.
Par exemple, la range
de l'exemple ci-dessus est itérable, mais pas semblable à un tableau, car elle n'a pas de propriétés indexées ni length
.
Et voici l'objet qui ressemble à un tableau, mais qui n'est pas itérable :
let arrayLike = { // a des index et une longueur => semblable à un tableau 0 : "Bonjour", 1 : "Monde", longueur : 2 } ; // Erreur (pas de Symbol.iterator) for (laisser l'élément de arrayLike) {}
Les itérables et les tableaux similaires ne sont généralement pas des tableaux , ils n'ont pas push
, pop
etc. C'est plutôt gênant si nous avons un tel objet et que nous voulons travailler avec lui comme avec un tableau. Par exemple, nous aimerions travailler avec range
en utilisant des méthodes de tableau. Comment y parvenir ?
Il existe une méthode universelle Array.from qui prend une valeur itérable ou de type tableau et en fait un « vrai » Array
. Ensuite, nous pouvons appeler des méthodes de tableau dessus.
Par exemple:
laissez arrayLike = { 0 : "Bonjour", 1 : "Monde", longueur : 2 } ; let arr = Array.from(arrayLike); // (*) alert(arr.pop()); // Monde (méthode fonctionne)
Array.from
à la ligne (*)
prend l'objet, l'examine pour déterminer s'il s'agit d'un itérable ou de type tableau, puis crée un nouveau tableau et y copie tous les éléments.
La même chose se produit pour un itérable :
// en supposant que cette plage est tirée de l'exemple ci-dessus let arr = Array.from(range); alerte(arr); // 1,2,3,4,5 (la conversion de tableau en chaîne fonctionne)
La syntaxe complète d' Array.from
nous permet également de fournir une fonction facultative de « mappage » :
Array.from(obj[, mapFn, thisArg])
Le deuxième argument facultatif mapFn
peut être une fonction qui sera appliquée à chaque élément avant de l'ajouter au tableau, et thisArg
nous permet de this
définir pour lui.
Par exemple:
// en supposant que cette plage est tirée de l'exemple ci-dessus // mettre au carré chaque nombre let arr = Array.from(range, num => num * num); alerte(arr); // 1,4,9,16,25
Ici, nous utilisons Array.from
pour transformer une chaîne en un tableau de caractères :
laissez str = '??'; // divise str en tableau de caractères let chars = Array.from(str); alerte(caractères[0]); // ? alerte(caractères[1]); // ? alert(chars.length); // 2
Contrairement à str.split
, il repose sur la nature itérable de la chaîne et donc, tout comme for..of
, fonctionne correctement avec des paires de substitution.
Techniquement, ici, cela fait la même chose que :
laissez str = '??'; laissez chars = []; // Array.from fait en interne la même boucle pour (laisser char de str) { chars.push(char); } alerte (caractères);
… Mais c'est plus court.
Nous pouvons même y créer slice
compatible avec les substituts :
fonction tranche (str, début, fin) { return Array.from(str).slice(start, end).join(''); } laissez str = '???'; alerte( slice(str, 1, 3) ); // ?? // la méthode native ne prend pas en charge les paires de substitution alert( str.slice(1, 3) ); // déchets (deux morceaux de paires de substitution différentes)
Les objets qui peuvent être utilisés dans for..of
sont appelés iterable .
Techniquement, les itérables doivent implémenter la méthode nommée Symbol.iterator
.
Le résultat de obj[Symbol.iterator]()
est appelé un Itérateur . Il gère le processus d'itération ultérieur.
Un itérateur doit avoir la méthode nommée next()
qui renvoie un objet {done: Boolean, value: any}
, ici done:true
désigne la fin du processus d'itération, sinon la value
est la valeur suivante.
La méthode Symbol.iterator
est appelée automatiquement par for..of
, mais nous pouvons également le faire directement.
Les itérables intégrés comme les chaînes ou les tableaux implémentent également Symbol.iterator
.
L'itérateur de chaîne connaît les paires de substitution.
Les objets qui ont des propriétés et length
indexées sont appelés de type tableau . De tels objets peuvent également avoir d'autres propriétés et méthodes, mais ne disposent pas des méthodes intégrées des tableaux.
Si nous regardons à l'intérieur de la spécification, nous verrons que la plupart des méthodes intégrées supposent qu'elles fonctionnent avec des itérables ou des tableaux au lieu de tableaux « réels », car c'est plus abstrait.
Array.from(obj[, mapFn, thisArg])
crée un véritable Array
à partir d'un obj
itérable ou de type tableau, et nous pouvons ensuite utiliser des méthodes de tableau dessus. Les arguments facultatifs mapFn
et thisArg
nous permettent d'appliquer une fonction à chaque élément.