🌐 AIæœçŽą & 代理 䞻饔
18 juin 2023

Iterables

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).

  1. Lorsque for..of dĂ©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Ă©thode next.
  2. À partir de lĂ , for..of ne fonctionne qu’avec cet objet retournĂ©.
  3. Quand for..of veut la valeur suivante, il appelle next() sur cet objet.
  4. Le rĂ©sultat de next() doit avoir la forme {done: Boolean, value: any}, oĂč done = true signifie que l’itĂ©ration est terminĂ©e, sinon value doit ĂȘ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 range lui-mĂȘme n’a pas la mĂ©thode next().
  • Au lieu de cela, un autre objet, appelĂ© “iterator”, est créé par l’appel Ă  range[Symbol.iterator](), et sa mĂ©thode next() 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.

Itérateurs infinis

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}, ici done: true dĂ©note la fin du processus de l’itĂ©ration, sinon value est la valeur suivante.
  • La mĂ©thode Symbol.iterator est appelĂ©e automatiquement par for..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.

Carte du tutoriel

Commentaires

lire ceci avant de commenter

  • Si vous avez des amĂ©liorations Ă  suggĂ©rer, merci de soumettre une issue GitHub ou une pull request au lieu de commenter.
  • Si vous ne comprenez pas quelque chose dans l'article, merci de prĂ©ciser.
  • Pour insĂ©rer quelques bouts de code, utilisez la balise <code>, pour plusieurs lignes – enveloppez-les avec la balise <pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepen
)