Cette fonction fait partie du standard ECMAScript 2015 (ES6)
Cette technologie a été finalisée mais il est préférable de consulter le tableau de compatibilité pour connaître l'état de l'implémentation dans les différents navigateurs.
Les protocoles d'itération
Le protocole « itérable »
Le protocole itérable permet à des objets JavaScript de définir le comportement qu'ils auront lorsqu'utilisés dans des boucles telles que for..of. Certains types natifs comme Array ou Map, disposent naturellement de fonctionnalités d'itérations. D'autres types, comme Object n'ont pas cette fonctionnalité.
Afin d'être itérable, un objet doit implémenter la méthode @@iterator. Cela signifie que l'objet doit disposer (directement ou via la chaîne de prototypes) d'une propriété identifiée par une clé Symbol.iterator :
| Propriété | Valeur |
|---|---|
[Symbol.iterator] |
Une fonction sans argument qui renvoie un objet respectant le protocole itérateur. |
Lorsqu'on doit itérer sur un objet (par exemple au début d'une boucle for..of), sa méthode @@iterator est appelée sans argument. L'objet renvoyé, un itérateur, est ensuite utilisé afin d'obtenir les valeurs sur lesquelles itérer.
Le protocole itérateur (iterator)
Le protocole itérateur définit une façon standard pour produire une suite de valeurs (finie ou infinie).
Un objet est considéré comme un itérateur s'il implémente la méthode next() avec la sémantique suivante :
| Propriété | Valeur |
|---|---|
next |
Une fonction sans argument qui renvoie un objet possédant deux propriétés :
|
Les itérateurs sont des itérables :
var unTableau = [1, 5, 7]; var élémentsTableaux = unTableau.entries(); élémentsTableaux.toString(); // "[object Array Iterator]" élémentsTableaux === élémentsTableaux[Symbol.iterator](); // true
Exemples sur les protocoles d'itération
Un objet String est une exemple d'objet natif itérable :
var uneChaîne = "yop"; typeof uneChaîne[Symbol.iterator] // "function"
Pour les objets String, l'itérateur par défaut renvoie les caractères qui composent la chaîne, un par un :
var iterator = uneChaîne[Symbol.iterator]();
iterator + "" // "[object String Iterator]"
iterator.next() // { value: "y", done: false }
iterator.next() // { value: "o", done: false }
iterator.next() // { value: "p", done: false }
iterator.next() // { value: undefined, done: true }
Certains opérateurs, comme l'opérateur de décomposition, utilisent le même protocole :
[...uneChaîne] // ["y", "o", "p"]
Il est possible de définir un autre comportement d'itération en fournissant son propre @@iterator:
var uneChaîne = new String("yop"); // on construit un objet String afin d'éviter la conversion automatique
uneChaîne[Symbol.iterator] = function() {
return { // l'objet itérateur, qui renvoie un seul élément, la chaîne "bye"
next: function() {
if (this._première) {
this._première = false;
return { value: "bye", done: false };
} else {
return { done: true };
}
},
_première: true
};
};
Attention, redéfinir le symbole @@iterator affecte le comportement des autres éléments du langage qui utilisent le protocole :
[...uneChaîne] // ["bye"] uneChaîne + "" // "yop"
Les itérables natifs
Les objets String, Array, Map, Set et Generator sont des itérables natifs. En effet, leurs prototypes ont une méthode @@iterator.
L'objet arguments est également un itérable natif. Il dispose d'une propriété @@iterator directe (il ne l'hérite pas de son prototype).
Les itérables construits
Il est possible de construire un itérable de la façon suivante :
var monItérable = {}
monItérable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myItérable] // [1, 2, 3]
Les API natives utilisant les itérables
Map([iterable]), WeakMap([iterable]), Set([iterable]), WeakSet([iterable]), ainsi que Promise.all(iterable), Promise.race(iterable) et Array.from() :
var monObj = {}
new Map([[1,"a"],[2,"b"],[3,"c"]]).get(2) // "b"
new WeakMap([[{},"a"],[monObj,"b"],[{},"c"]]).get(monObj) // "b"
new Set([1, 2, 3]).has(3) // true
new Set("123").has("2") // true
new WeakSet(function*() {
yield {};
yield monObj;
yield {};
}()).has(monObj) // true
Les opérateurs et syntaxes utilisant les itérables
for-of, l'opérateur de décomposition, yield*, la déstructuration :
for(let value of ["a", "b", "c"]){
console.log(value)
}
// "a"
// "b"
// "c"
[..."abc"] // ["a", "b", "c"]
function* gen(){
yield* ["a", "b", "c"]
}
gen().next() // { value:"a", done:false }
[a, b, c] = new Set(["a", "b", "c"])
a // "a"
Les itérables mal-formés
Si la méthode @@iterator d'un objet ne renvoie pas un itérateur, on a alors un itérable mal formé. Utiliser un tel objet peut entraîner des exceptions ou un comportement erratique:
var itérableMalFormé = {}
itérableMalFormé[Symbol.iterator] = () => 1
[...itérableMalFormé] // TypeError: [] is not a function
Un générateur est-il un itérateur ou un itérable ?
La réponse est : un générateur est à la fois un itérateur et un itérable.
var aGeneratorObject = function*(){
yield 1;
yield 2;
yield 3;
}()
typeof aGeneratorObject.next
// "function", possède une méthode next, c'est donc un itérateur
typeof aGeneratorObject[Symbol.iterator]
// "function", car il possède une méthode @@iterator, c'est donc un itérable
aGeneratorObject[Symbol.iterator]() === aGeneratorObject
// true, car @@iterator renvoie l'objet même (un itérateur), c'est donc un itérable bien formé
[...aGeneratorObject]
// [1, 2, 3]