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]