Vérifier si quelque chose est itérable


104

Dans la documentation MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of

La for...ofconstruction est décrite pour pouvoir itérer sur des objets "itérables". Mais y a-t-il un bon moyen de décider si un objet est itérable?

J'ai essayé de trouver des propriétés communes pour les tableaux, les itérateurs et les générateurs, mais je n'ai pas pu le faire.

En plus de faire un for ... ofbloc d'essai et de vérifier les erreurs de type, existe-t-il un moyen propre de le faire?


Sûrement, en tant qu'auteur, savez-vous si votre objet est itérable?
andrewb

5
L'objet est passé en argument, je n'en suis pas sûr.
simonzack

1
Pourquoi ne pas tester le typeof de l'argument?
James Bruckner

2
@ andrew-buchan, James Bruckner: La vérification des types peut fonctionner, mais si vous lisez la documentation MDN, vous remarquerez qu'elle dit "en forme de tableau". Je ne sais pas ce que cela signifie exactement, d'où la question.
simonzack

1
wiki.ecmascript.org/doku.php?id=harmony:iterators déclare " Un objet est itérable s'il a une iterator()méthode. ". Cependant, puisqu'il s'agit d'un brouillon, seul un contrôle peut dépendre de la mise en œuvre. Quel environnement utilisez-vous?
Bergi

Réponses:


142

La bonne façon de vérifier l'itérabilité est la suivante:

function isIterable(obj) {
  // checks for null and undefined
  if (obj == null) {
    return false;
  }
  return typeof obj[Symbol.iterator] === 'function';
}

Pourquoi cela fonctionne (protocole itératif en profondeur): https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols

Puisque nous parlons de for..of, je suppose, nous sommes dans l'état d'esprit ES6.

De plus, ne soyez pas surpris que cette fonction retourne truesi objest une chaîne, car les chaînes parcourent leurs caractères.


14
ou Symbol.iterator in Object(obj),.

14
Il existe (au moins) une exception à l'utilisation de l'opérateur «in»: chaîne. La chaîne est itérable (en termes de for..of) mais vous ne pouvez pas utiliser «in» dessus. Sinon, je préférerais utiliser «in», il semble nettement plus joli.
Tomas Kulich

ça ne devrait pas être return typeof obj[Symbol.iterator] === 'function'? "Pour être itérable, un objet doit implémenter la méthode @@ iterator" - il spécifie la méthode
callum

Il n'y a pas de sémantique appropriée pour que obj [Symbol.iterator] soit autre chose qu'une fonction (non définie ou). Si quelqu'un y met par exemple String, c'est une mauvaise chose et IMO c'est bien si le code échoue le plus tôt possible.
Tomas Kulich

Serait typeof Object(obj)[Symbol.iterator] === 'function'travailler dans tous les cas?
Craig Gidney

26

Pourquoi si verbeux?

const isIterable = object =>
  object != null && typeof object[Symbol.iterator] === 'function'

57
Readability > Clevernessrevient toujours true.
jfmercer

27
Haha, normalement c'est moi qui me plains du code illisible, mais en fait je pense que c'est assez lisible. Comme une phrase en anglais: si l'objet n'est pas nul et que la propriété d'itérateur symbolique est une fonction, alors il est itérable. Si ce n'est pas simple, je ne sais pas ce que c'est ...
Adius

4
imo, le but de la "lisibilité" est de comprendre ce qui se passe sans lecture réelle
Dmitry Parzhitsky

2
@Alexander Mills Votre amélioration a rendu le code pire. 1. Comme @jfmercer l'a dit Readability > Cleverness, raccourcir la variable objectà on'aide personne. 2. Une chaîne vide ''est itérable et doit donc retourner true.
adius

1
Compris, j'ai pensé qu'il y avait une raison pour laquelle il utilisait!= null
Alexander Mills

22

La solution la plus simple est en fait la suivante:

function isIterable (value) {
  return Symbol.iterator in Object(value);
}

Objectenveloppera tout ce qui n'est pas un objet en un, permettant à l' inopérateur de travailler même si la valeur d'origine n'est pas un objet. nullet undefinedsont transformés en objets vides, il n'est donc pas nécessaire de détecter la casse des bords, et les chaînes sont enveloppées dans des objets String qui sont itérables.


1
Vous avez utilisé le mauvais symbole. Il est: Symbol.iterator.
Gil

@Gil Vous avez absolument raison, oups! J'aurais dû copier-coller du code testé au lieu de taper directement dans un message.
Domino

La création d'un nouvel objet avec Objectest un gaspillage pour ce type de contrôle.
Ruben Verborgh le

Je ne peux pas dire que je sais exactement comment la Objectfonction est implémentée, mais cela ne crée un nouvel objet que s'il valuen'est pas déjà un objet. Je m'attendrais à ce que la combinaison de inet Object(...)soit quelque chose que les moteurs de navigateur peuvent facilement optimiser, contrairement à ce que je dis value !== undefined && value !== null && value[Symbol.iterator] && true. De plus, c'est extrêmement lisible, ce qui me tient à cœur.
Domino le

9

En tant que sidenote, méfiez - vous de la définition de itérables . Si vous venez d'autres langues, vous vous attendez à ce que quelque chose sur lequel vous pouvez itérer, disons, une forboucle soit itérable . J'ai bien peur que ce ne soit pas le cas ici où iterable signifie quelque chose qui implémente le protocole d'itération .

Pour rendre les choses plus claires, tous les exemples ci-dessus retournent falsesur cet objet {a: 1, b: 2}car cet objet n'implémente pas le protocole d'itération. Vous ne pourrez donc pas le parcourir avec un for...of MAIS vous le pouvez toujours avec un for...in.

Donc, si vous voulez éviter des erreurs douloureuses, rendez votre code plus spécifique en renommant votre méthode comme indiqué ci-dessous:

/**
 * @param variable
 * @returns {boolean}
 */
const hasIterationProtocol = variable =>
    variable !== null && Symbol.iterator in Object(variable);

Vous n'avez aucun sens. Pourquoi pensez-vous que cela romprait avec undefined?
adius

@adius Je pense que j'ai supposé à tort que vous faisiez object !== nulldans votre réponse, mais vous le faites object != null, ce n'est pas en rupture avec undefinedce cas précis. J'ai mis à jour ma réponse en conséquence.
Francesco Casula

OK je vois. Btw: Votre code est incorrect. hasIterationProtocol('')doit revenir true! Que diriez-vous de supprimer votre code et de laisser simplement la section d'explication itérative, qui est la seule chose qui ajoute une valeur réelle / quelque chose de nouveau dans votre réponse.
adius

1
Cela revient true, en supprimant une partie de l'ancienne réponse, j'ai fusionné les deux fonctions et j'ai oublié la comparaison stricte. Maintenant, j'ai remis la réponse originale qui fonctionnait bien.
Francesco Casula

1
Je n'aurais jamais pensé à utiliser Object(...), belle prise. Mais dans ce cas, le contrôle nul n'est pas nécessaire.
Domino

2

De nos jours, comme déjà indiqué, pour tester s'il objest itérable, faites simplement

obj != null && typeof obj[Symbol.iterator] === 'function' 

Réponse historique (pas plus valide)

La for..ofconstruction fait partie du projet de spécification du langage ECMASCript 6e édition. Cela pourrait donc changer avant la version finale.

Dans ce brouillon, les objets itérables doivent avoir la fonction en iteratortant que propriété.

Vous pouvez vérifier si un objet est itérable comme ceci:

function isIterable(obj){
   if(obj === undefined || obj === null){
      return false;
   }
   return obj.iterator !== undefined;
}

7
La propriété iterator était une chose temporaire de Firefox. Ce n'est pas conforme à ES6. voir: developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Amir Arad

2

Pour les itérateurs asynchrones, vous devriez vérifier le 'Symbol.asyncIterator' au lieu de 'Symbol.iterator':

async function* doSomething(i) {
    yield 1;
    yield 2;
}

let obj = doSomething();

console.log(typeof obj[Symbol.iterator] === 'function');      // false
console.log(typeof obj[Symbol.asyncIterator] === 'function'); // true

1

Si vous vouliez vérifier en fait si une variable est un objet ( {key: value}) ou un tableau ( [value, value]), vous pouvez le faire:

const isArray = function (a) {
    return Array.isArray(a);
};

const isObject = function (o) {
    return o === Object(o) && !isArray(o) && typeof o !== 'function';
};

function isIterable(variable) {
    return isArray(variable) || isObject(variable);
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.