iterable

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 :

  • done (un booléan)
    • cette propriété vaut true lorsque l'itérateur a terminé la suite à parcourir. Dans ce cas, la valeur value définit éventuellement la valeur de retour pour l'itéraeur. Plus de détails sur les valeurs de retour dans cet article.
    • cette propriété vaut false lorsque l'itérateur a pu fournir la prochaine valeur pour la suite. Ne pas fournir de propriété done est considéré comme équivalent à fournir une propriété done à false.
  • value : une valeur JavaScript quelconque renvoyée par l'itérateur. Cette propriété peut ne pas être définie lorsque done vaut true.

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]

Étiquettes et contributeurs liés au document

 Contributeurs à cette page : SphinxKnight
 Dernière mise à jour par : SphinxKnight,