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âŠ)