Les objets Iterable sont une gnralisation des tableaux. Cest un concept qui permet de rendre nimporte quel objet utilisable dans une boucle for..of.
Bien sr, les tableaux sont itrables. Mais il existe de nombreux autres objets intgrs, qui sont galement itrables. Par exemple, les chanes de caractres sont galement itrables.
Si un objet nest pas techniquement un tableau, mais reprsente 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 itrables en faisant le ntre.
Par exemple, nous avons un objet qui nest pas un tableau, mais qui semble convenir pour une boucle for..of.
Comme un objet range qui reprsente 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 itrable (et donc laisser for..of faire son travail), nous devons ajouter une mthode lobjet nomm Symbol.iterator (un symbole intgr spcial que pour cela).
- Lorsque
for..ofdmarre, il appelle cette mthode une fois (ou des erreurs si il nest pas trouv). La mthode doit retourner un iterator un objet avec la mthodenext. - partir de l,
for..ofne fonctionne quavec cet objet retourn. - Quand
for..ofveut la valeur suivante, il appellenext()sur cet objet. - Le rsultat de
next()doit avoir la forme{done: Boolean, value: any}, odone = truesignifie que litration est termine, sinonvaluedoit tre la nouvelle valeur.
Voici limplmentation complte 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 itrateur :
// 2. partir de l, for..of fonctionne uniquement avec cet itrateur, lui demandant les valeurs suivantes
return {
current: this.from,
last: this.to,
// 3. next() est appele chaque itration 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 (sparation des proccupations).
- Le
rangelui-mme na pas la mthodenext(). - Au lieu de cela, un autre objet, appel iterator, est cr par lappel
range[Symbol.iterator](), et sa mthodenext()gnre des valeurs pour litration.
Ainsi, lobjet itrateur est spar de lobjet sur lequel il est itr.
Techniquement, nous pouvons les fusionner et utiliser range lui-mme comme itrateur 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 lobjet range lui-mme : il dispose de la mthode next() et se souvient de la progression de litration en cours dans this.current. Cest plus court? Oui. Et parfois cest aussi bien.
Linconvnient est quil est maintenant impossible davoir deux boucles for..of sexcutant simultanment sur lobjet: elles partageront ltat ditration, car il ny a quun seul itrateur lobjet lui-mme. Cependant, il est rare de disposer de deux for-of parallles, faisables avec certains scnarios asynchrones.
Des itrateurs infinis sont galement possibles. Par exemple, range devient infini pour range.to = Infinity. Ou nous pouvons crer un objet itrable qui gnre une suite infinie de nombres pseudo-alatoires. Il peut tre aussi utile.
Il ny a pas de limitation sur next, il peut renvoyer de plus en plus de valeurs, cest normal.
Bien sr, la boucle for..of sur une telle itration serait sans fin. Mais on peut toujours larrter en utilisant break.
String est iterable
Les tableaux et les chanes de caractres sont les iterables intgrs les plus largement utiliss.
Pour une chane de caractres, for..of boucle sur ses caractres :
for (let char of "test") {
// se dclenche 4 fois: une fois pour chaque caractre
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 itrateur
Pour une comprhension plus approfondie, voyons comment utiliser explicitement un itrateur.
Nous allons parcourir une chane de caractres de la mme manire que for..of, mais avec des appels directs. Ce code cre un itrateur de chane de caractres et en rcupre la valeur manuellement :
let str = "Hello";
// fait la mme 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 caractres un par un
}
Cela est rarement ncessaire, mais nous donne plus de contrle sur le processus que for..of. Par exemple, nous pouvons scinder le processus ditration : itrer un peu, puis arrter, faire autre chose, puis reprendre plus tard.
Iterables et array-likes
Il existe deux termes officiels qui se ressemblent mais qui sont trs diffrents. Assurez-vous de bien les comprendre pour viter la confusion.
- Iterables sont des objets qui implmentent la mthode
Symbol.iterator, comme dcrit 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 tches pratiques dans un navigateur ou dautres environnements, il est possible que nous rencontrions des objets qui sont iterables ou des array-like, ou les deux.
Par exemple, les chanes de caractres sont la fois iterables (for..of fonctionne dessus) et des array-likes (elles ont des index numriques et une longueur).
Mais un itrable peut ne pas ressembler un array-like. Et inversement, un array-like peut ne pas tre itrable.
Par exemple, la range dans lexemple ci-dessus est itrable, mais pas comme un array-like, car elle na pas de proprits indexes et de length.
Et voici lobjet qui ressemble un tableau, mais pas itrable :
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 gnralement pas des tableaux, ils nont pas push, pop, etc. Cest plutt gnant 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 mthodes de tableau. Comment y parvenir ?
Array.from
Il existe une mthode universelle Array.from qui prend une valeur itrable ou array-like et en fait un vrai Array. Ensuite, nous pouvons appeler des mthodes de tableau dessus.
Par exemple :
let arrayLike = {
0: "Hello",
1: "World",
length: 2
};
let arr = Array.from(arrayLike); // (*)
alert(arr.pop()); // World (la mthode fonctionne)
Array.from la ligne (*) prend lobjet, lexamine comme tant un objet itrable ou un array-like, cre ensuite un nouveau tableau et y copie tous les lments.
Il en va de mme pour un itrable :
// en supposant que cette "range" est tire de l'exemple ci-dessus
let arr = Array.from(range);
alert(arr); // 1,2,3,4,5 (array toString conversion fonctionne)
La syntaxe complte 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 lment avant de lajouter au tableau, et thisArg permet den dfinir le this.
Par exemple :
// en supposant que cette "range" est tire 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 chane en un tableau de caractres :
let str = '';
// divise une chane en un tableau de caractres
let chars = Array.from(str);
alert(chars[0]); //
alert(chars[1]); //
alert(chars.length); // 2
Contrairement str.split, il repose sur la nature itrable de la chane et donc, tout comme for..of, fonctionne correctement avec des paires de substitution.
Techniquement, il fait la mme chose que :
let str = '';
let chars = []; // Array.from en interne fait la mme boucle
for (let char of str) {
chars.push(char);
}
alert(chars);
Mais cest plus court.
Nous pouvons mme crer 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 mthodes native ne supporte pas les paires de substitution
alert( str.slice(1, 3) ); // ordures (deux pices de paires de substitution diffrentes)
Rsum
Les objets pouvant tre utiliss dans for..of sappellent iterable.
- Techniquement, les iterables doivent implmenter la mthode nomme
Symbol.iterator.- Le rsultat de
obj[Symbol.iterator]()sappelle un itrateur. Il gre le processus ditration ultrieur. - Un itrateur doit avoir la mthode nomme
next()qui retourne un objet{done: Boolean, value: any}, icidone: truednote la fin du processus de litration, sinonvalueest la valeur suivante.
- Le rsultat de
- La mthode
Symbol.iteratorest appele automatiquement parfor..of, mais nous pouvons aussi le faire directement. - Les iterables intgrs tels que des chanes de caractres ou des tableaux implmentent galement
Symbol.iterator. - Litrateur de chane de caractres connat les paires de substitution.
Les objets qui ont des proprits indexes et des length sont appels array-like. De tels objets peuvent galement avoir dautres proprits et mthodes, mais ne possdent pas les mthodes intgres des tableaux.
Si nous regardons lintrieur de la spcification nous verrons que la plupart des mthodes intgres supposent quelles fonctionnent avec des lments iterables ou des array-like au lieu de vrais tableaux, car cest plus abstrait.
Array.from(obj[, mapFn, thisArg]) crer un vritable Array partir dun obj itrable ou array-like, et nous pouvons ensuite utiliser des mthodes de tableau sur celui-ci. Les arguments optionnels mapFn et thisArg nous permettent dappliquer une fonction chaque lment.
Commentaires
<code>, pour plusieurs lignes enveloppez-les avec la balise<pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepen)