Les objets Iterable sont une généralisation des tableaux. Câest un concept qui 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 de caractères sont également itérables.
Si un objet nâest pas techniquement un tableau, mais représente une collection (liste, set) de quelque chose, alors for..of est une excellente syntaxe pour boucler dessus, voyons comment le faire fonctionner.
Symbol.iterator
Nous pouvons facilement saisir le concept des itérables en faisant le nôtre.
Par exemple, nous avons un objet qui nâest pas un tableau, mais qui semble convenir pour une boucle for..of.
Comme un objet range qui représente un intervalle de nombres :
let range = {
from: 1,
to: 5
};
// Nous voulons que le for..of fonctionne :
// for (let num of range) ... num=1,2,3,4,5
Pour rendre la range itérable (et donc laisser for..of faire son travail), nous devons ajouter une méthode à lâobjet nommé Symbol.iterator (un symbole intégré spécial que pour cela).
- Lorsque
for..ofdémarre, il appelle cette méthode une fois (ou des erreurs si il nâest pas trouvé). La méthode doit retourner un iterator â un objet avec la méthodenext. - à partir de là ,
for..ofne fonctionne quâavec cet objet retourné. - Quand
for..ofveut la valeur suivante, il appellenext()sur cet objet. - Le résultat de
next()doit avoir la forme{done: Boolean, value: any}, oùdone = truesignifie que lâitération est terminée, sinonvaluedoit être la nouvelle valeur.
Voici lâimplémentation complète de range avec les remarques :
let range = {
from: 1,
to: 5
};
// 1. l'appel d'un for..of appelle initialement ceci
range[Symbol.iterator] = function() {
// ...il retourne l'objet itérateur :
// 2. à partir de là , for..of fonctionne uniquement avec cet itérateur, lui demandant les valeurs suivantes
return {
current: this.from,
last: this.to,
// 3. next() est appelée à chaque itération par la boucle for..of
next() {
// 4. il devrait renvoyer la valeur sous forme d'objet {done: .., valeur: ...}
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
};
// maintenant ça marche !
for (let num of range) {
alert(num); // 1, ensuite 2, 3, 4, 5
}
Veuillez noter la fonctionnalité principale des iterables : separation of concerns (séparation des préoccupations).
- Le
rangelui-même nâa pas la méthodenext(). - Au lieu de cela, un autre objet, appelé âiteratorâ, est créé par lâappel Ã
range[Symbol.iterator](), et sa méthodenext()génère des valeurs pour lâitération.
Ainsi, lâobjet itérateur est séparé de lâobjet sur lequel il est itéré.
Techniquement, nous pouvons les fusionner et utiliser range lui-même comme itérateur pour simplifier le code.
Comme ça :
let range = {
from: 1,
to: 5,
[Symbol.iterator]() {
this.current = this.from;
return this;
},
next() {
if (this.current <= this.to) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
for (let num of range) {
alert(num); // 1, ensuite 2, 3, 4, 5
}
Maintenant, range[Symbol.iterator]() renvoie lâobjet range lui-même : il dispose de la méthode next() et se souvient de la progression de lâitération en cours dans this.current. Câest plus court? Oui. Et parfois câest aussi bien.
Lâinconvénient est quâil est maintenant 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. Cependant, il est rare de disposer de deux for-of parallèles, faisables avec certains scénarios asynchrones.
Des itérateurs infinis sont également possibles. Par exemple, range devient infini pour range.to = Infinity. Ou nous pouvons créer un objet itérable qui génère une suite infinie de nombres pseudo-aléatoires. Il peut être aussi utile.
Il nây a pas de limitation sur next, il peut renvoyer de plus en plus de valeurs, câest normal.
Bien sûr, la boucle for..of sur une telle itération serait sans fin. Mais on peut toujours lâarrêter en utilisant break.
String est iterable
Les tableaux et les chaînes de caractères sont les iterables intégrés les plus largement utilisés.
Pour une chaîne de caractères, for..of boucle sur ses caractères :
for (let char of "test") {
// se déclenche 4 fois: une fois pour chaque caractère
alert( char ); // t, ensuite e, ensuite s, ensuite t
}
Et cela fonctionne correctement avec les paires de substitution !
let str = 'ð³ð';
for (let char of str) {
alert( char ); // ð³, et ensuite ð
}
Appeler explicitement un itérateur
Pour une compréhension plus approfondie, voyons comment utiliser explicitement un itérateur.
Nous allons parcourir une chaîne de caractères de la même manière que for..of, mais avec des appels directs. Ce code crée un itérateur de chaîne de caractères et en récupère la valeur âmanuellementâ :
let str = "Hello";
// fait la même chose que
// for (let char of str) alert(char);
let iterator = str[Symbol.iterator]();
while (true) {
let result = iterator.next();
if (result.done) break;
alert(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 scinder le processus dâitération : itérer un peu, puis arrêter, faire autre chose, puis reprendre plus tard.
Iterables et array-likes
Il existe deux termes officiels qui se ressemblent mais qui sont très différents. Assurez-vous de bien les comprendre pour éviter la confusion.
- Iterables sont des objets qui implémentent la méthode
Symbol.iterator, comme décrit ci-dessus. - Array-likes sont des objets qui ont des index et des
length, ils ressemblent donc à des tableaux.
Lorsque nous utilisons JavaScript pour des tâches pratiques dans un navigateur ou dâautres environnements, il est possible que nous rencontrions des objets qui sont iterables ou des array-like, ou les deux.
Par exemple, les chaînes de caractères sont à la fois iterables (for..of fonctionne dessus) et des array-likes (elles ont des index numériques et une longueur).
Mais un itérable peut ne pas ressembler à un array-like. Et inversement, un array-like peut ne pas être itérable.
Par exemple, la range dans lâexemple ci-dessus est itérable, mais pas comme un array-like, car elle nâa pas de propriétés indexées et de length.
Et voici lâobjet qui ressemble à un tableau, mais pas itérable :
let arrayLike = { // a des index et une longueur => semblable à un tableau
0: "Hello",
1: "World",
length: 2
};
// Erreur (pas de Symbol.iterator)
for (let item of arrayLike) {}
Les iterables et les array-likes 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 une plage en utilisant les méthodes de tableau. Comment y parvenir ?
Array.from
Il existe une méthode universelle Array.from qui prend une valeur itérable ou array-like et en fait un âvraiâ Array. Ensuite, nous pouvons appeler des méthodes de tableau dessus.
Par exemple :
let arrayLike = {
0: "Hello",
1: "World",
length: 2
};
let arr = Array.from(arrayLike); // (*)
alert(arr.pop()); // World (la méthode fonctionne)
Array.from à la ligne (*) prend lâobjet, lâexamine comme étant un objet itérable ou un array-like, crée ensuite un nouveau tableau et y copie tous les éléments.
Il en va de même pour un itérable :
// en supposant que cette "range" est tirée de l'exemple ci-dessus
let arr = Array.from(range);
alert(arr); // 1,2,3,4,5 (array toString conversion fonctionne)
La syntaxe complète de Array.from permet aussi de fournir une fonction optionnelle de âmappingâ :
Array.from(obj[, mapFn, thisArg])
Le second argument mapFn peut être une fonction à appliquer à chaque élément avant de lâajouter au tableau, et thisArg permet dâen définir le this.
Par exemple :
// en supposant que cette "range" est tirée de l'exemple ci-dessus
// met au carré chaque nombre
let arr = Array.from(range, num => num * num);
alert(arr); // 1,4,9,16,25
Ici, nous utilisons Array.from pour transformer une chaîne en un tableau de caractères :
let str = 'ð³ð';
// divise une chaîne en un tableau de caractères
let chars = Array.from(str);
alert(chars[0]); // ð³
alert(chars[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, il fait la même chose que :
let str = 'ð³ð';
let chars = []; // Array.from en interne fait la même boucle
for (let char of str) {
chars.push(char);
}
alert(chars);
â¦Mais câest plus court.
Nous pouvons même créer une fonction slice qui prend en compte les paires de substitution :
function slice(str, start, end) {
return Array.from(str).slice(start, end).join('');
}
let str = 'ð³ðð©·¶';
alert( slice(str, 1, 3) ); // ðð©·¶
// les méthodes native ne supporte pas les paires de substitution
alert( str.slice(1, 3) ); // ordures (deux pièces de paires de substitution différentes)
Résumé
Les objets pouvant être utilisés dans for..of sâappellent iterable.
- Techniquement, les iterables doivent implémenter la méthode nommée
Symbol.iterator.- Le résultat de
obj[Symbol.iterator]()sâappelle 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 retourne un objet{done: Boolean, value: any}, icidone: truedénote la fin du processus de lâitération, sinonvalueest la valeur suivante.
- Le résultat de
- La méthode
Symbol.iteratorest appelée automatiquement parfor..of, mais nous pouvons aussi le faire directement. - Les iterables intégrés tels que des chaînes de caractères ou des tableaux implémentent également
Symbol.iterator. - Lâitérateur de chaîne de caractères connaît les paires de substitution.
Les objets qui ont des propriétés indexées et des length sont appelés array-like. De tels objets peuvent également avoir dâautres propriétés et méthodes, mais ne possèdent pas les 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 éléments iterables ou des array-like au lieu de âvraisâ tableaux, car câest plus abstrait.
Array.from(obj[, mapFn, thisArg]) créer un véritable Array à partir dâun obj itérable ou array-like, et nous pouvons ensuite utiliser des méthodes de tableau sur celui-ci. Les arguments optionnels mapFn et thisArg nous permettent dâappliquer une fonction à chaque élément.
Commentaires
<code>, pour plusieurs lignes â enveloppez-les avec la balise<pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepenâ¦)