Verificare se qualcosa è iterabile


104

Nei documenti MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of

Il for...ofcostrutto è descritto per essere in grado di iterare su oggetti "iterabili". Ma c'è un buon modo per decidere se un oggetto è iterabile?

Ho provato a trovare proprietà comuni per array, iteratori e generatori, ma non sono stato in grado di farlo.

A parte l'esecuzione for ... ofdi un blocco try e il controllo degli errori di tipo, esiste un modo pulito per farlo?


Sicuramente, come autore, sai se il tuo oggetto è iterabile?
andrewb

5
L'oggetto viene passato come argomento, non ne sono sicuro.
simonzack

1
Perché non testare il tipo di argomento?
James Bruckner

2
@ andrew-buchan, James Bruckner: il controllo dei tipi può funzionare, ma se leggi i documenti MDN, noterai che dice "array-like". Non so cosa significhi esattamente, da qui la domanda.
simonzack

1
wiki.ecmascript.org/doku.php?id=harmony:iterators afferma " Un oggetto è iterabile se ha un iterator()metodo. ". Tuttavia, poiché si tratta di una bozza, solo un controllo potrebbe dipendere dall'implementazione. Che ambiente usi?
Bergi

Risposte:


142

Il modo corretto per verificare l'iterabilità è il seguente:

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

Perché funziona (protocollo iterabile in profondità): https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols

Dato che stiamo parlando di per ... di, presumo, siamo nella mentalità ES6.

Inoltre, non sorprenderti se questa funzione restituisce trueif objè una stringa, poiché le stringhe ripetono i loro caratteri.


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

14
C'è (almeno) un'eccezione all'uso dell'operatore "in": stringa. String è iterabile (in termini di for..of) ma non puoi usare "in" su di essa. Se non fosse per questo, preferirei usare "in" perché sembra decisamente più bello.
Tomas Kulich

non dovrebbe essere return typeof obj[Symbol.iterator] === 'function'? "Per essere iterabile, un oggetto deve implementare il metodo @@ iteratore" - specifica il metodo
callum

Non esiste una semantica appropriata per obj [Symbol.iterator] che sia qualcosa di diverso dalla funzione (non definita o). Se qualcuno mette ad esempio String lì, è una brutta cosa e IMO va bene se il codice fallisce il prima possibile.
Tomas Kulich

Funzionerebbe typeof Object(obj)[Symbol.iterator] === 'function'in tutti i casi?
Craig Gidney

26

Perché così prolisso?

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

57
Readability > Clevernessritorna sempre true.
jfmercer

27
Haha, normalmente sono io a lamentarmi del codice illeggibile, ma in realtà penso che sia abbastanza leggibile. Come una frase inglese: se l'oggetto non è nullo e la proprietà iteratore simbolico è una funzione, allora è iterabile. Se non è semplicissimo, non so cosa sia ...
adius

4
imo, il punto della "leggibilità" è capire cosa sta succedendo senza una lettura effettiva
Dmitry Parzhitsky

2
@Alexander Mills Il tuo miglioramento ha peggiorato il codice. 1. Come ha detto @jfmercer Readability > Cleverness, ridurre la variabile objecta onon aiuta nessuno. 2. Una stringa vuota ''è iterabile e quindi deve restituire true.
adius

1
Capito, ho pensato che ci fosse un motivo per cui stava usando!= null
Alexander Mills

22

La soluzione più semplice è in realtà questa:

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

Objectavvolgerà tutto ciò che non è un oggetto in uno, consentendo inall'operatore di lavorare anche se il valore originale non è un oggetto. nulle undefinedvengono trasformati in oggetti vuoti, quindi non è necessario il rilevamento dei casi limite e le stringhe vengono racchiuse in oggetti String che sono iterabili.


1
Hai usato il simbolo sbagliato. E ': Symbol.iterator.
Gil

@ Gil Hai perfettamente ragione, oops! Avrei dovuto copiare il codice testato e incollarlo invece di digitare direttamente in un post.
Domino

La creazione di un nuovo oggetto con Objectè uno spreco per questo tipo di controllo.
Ruben Verborgh,

Non posso dire di sapere esattamente come Objectviene implementata la funzione, ma crea solo un nuovo oggetto se valuenon è già un oggetto. Mi aspetto che la combinazione di ine Object(...)sia qualcosa che i motori dei browser possano facilmente ottimizzare, a differenza di dire value !== undefined && value !== null && value[Symbol.iterator] && true. Inoltre, è estremamente leggibile, cosa che mi interessa.
Domino

9

Come nota a margine, FARE ATTENZIONE alla definizione di iterabile . Se vieni da altre lingue ti aspetteresti che qualcosa su cui puoi iterare, diciamo, un forciclo sia iterabile . Temo che non sia questo il caso in cui iterabile significa qualcosa che implementa il protocollo di iterazione .

Per rendere le cose più chiare, tutti gli esempi precedenti tornano falsesu questo oggetto {a: 1, b: 2}perché quell'oggetto non implementa il protocollo di iterazione. Quindi non sarai in grado di iterare su di esso con un for...of MA puoi ancora farlo con un file for...in.

Quindi, se vuoi evitare errori dolorosi, rendi il tuo codice più specifico rinominando il tuo metodo come mostrato di seguito:

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

Non hai senso. Perché pensi che romperebbe con undefined?
adius

@adius Penso di aver erroneamente pensato che stavi facendo object !== nullnella tua risposta, ma lo stai facendo object != nullquindi non si rompe undefinedin quel caso specifico. Ho aggiornato di conseguenza la mia risposta.
Francesco Casula

Va bene, ho capito. Btw: il tuo codice non è corretto. hasIterationProtocol('')deve tornare true! Che ne dici di rimuovere il codice e lasciare la sezione di spiegazione iterabile, che è l'unica cosa che aggiunge valore reale / qualcosa di nuovo alla tua risposta.
adius

1
Ritorna true, rimuovendo parte della vecchia risposta ho unito entrambe le funzioni e ho dimenticato il confronto rigoroso. Ora ho rimesso a posto la risposta originale che funzionava bene.
Francesco Casula

1
Non avrei mai pensato di usare Object(...), bella cattura. Ma in tal caso il controllo nullo non è necessario.
Domino

2

Al giorno d'oggi, come già detto, per verificare se objè iterabile basta farlo

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

Risposta storica (non più valida)

Il for..ofcostrutto fa parte del progetto di specifica della lingua di ECMASCript 6a edizione. Quindi potrebbe cambiare prima della versione finale.

In questa bozza, gli oggetti iterabili devono avere la funzione iteratordi proprietà.

Puoi controllare se un oggetto è iterabile in questo modo:

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

7
La proprietà iteratore era una cosa temporanea di Firefox. Non è conforme a ES6. vedi: developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Amir Arad

2

Per gli iteratori asincroni dovresti controllare "Symbol.asyncIterator" invece di "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

Se volessi verificare in effetti se una variabile è un object ( {key: value}) o un array ( [value, value]), potresti farlo:

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);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.