Überprüfen, ob etwas iterierbar ist

104

In den MDN-Dokumenten: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of

Es for...ofwird beschrieben, dass das Konstrukt über "iterierbare" Objekte iterieren kann. Aber gibt es eine gute Möglichkeit zu entscheiden, ob ein Objekt iterierbar ist?

Ich habe versucht, gemeinsame Eigenschaften für Arrays, Iteratoren und Generatoren zu finden, konnte dies jedoch nicht.

Gibt for ... ofes eine saubere Möglichkeit, einen Try-Block auszuführen und nach Typfehlern zu suchen?

Simonzack
quelle
Sicher wissen Sie als Autor, ob Ihr Objekt iterierbar ist?
Andrew
5
Das Objekt wird als Argument übergeben, da bin ich mir nicht sicher.
Simonzack
1
Warum nicht den Typ des Arguments testen?
James Bruckner
2
@ andrew-buchan, James Bruckner: Das Überprüfen auf Typen funktioniert möglicherweise, aber wenn Sie die MDN-Dokumente lesen, werden Sie feststellen, dass dort "array-like" steht. Ich weiß nicht genau, was das bedeutet, daher die Frage.
Simonzack
1
wiki.ecmascript.org/doku.php?id=harmony:iterators gibt an, dass ein Objekt iterierbar ist, wenn es eine iterator()Methode hat . Da es sich jedoch um einen Entwurf handelt, kann nur eine Überprüfung implemenatationsabhängig sein. Welche Umgebung verwenden Sie?
Bergi

Antworten:

141

Der richtige Weg, um die Iterierbarkeit zu überprüfen, ist wie folgt:

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

Warum dies funktioniert (iterierbares Protokoll im Detail): https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols

Ich gehe davon aus, dass wir uns in der ES6-Denkweise befinden.

Seien Sie auch nicht überrascht, dass diese Funktion zurückgegeben wird, truewenn objes sich um eine Zeichenfolge handelt, da Zeichenfolgen über ihre Zeichen iterieren.

Tomas Kulich
quelle
14
oder , Symbol.iterator in Object(obj).
14
Es gibt (mindestens) eine Ausnahme bei der Verwendung des Operators 'in': string. String ist iterierbar (in Bezug auf for..of), aber Sie können nicht 'in' darauf verwenden. Wenn nicht, würde ich es vorziehen, 'in' zu verwenden, es sieht definitiv besser aus.
Tomas Kulich
sollte es nicht sein return typeof obj[Symbol.iterator] === 'function'? "Um iterierbar zu sein, muss ein Objekt die @@ iterator-Methode implementieren" - es gibt die Methode an
callum
Es gibt keine richtige Semantik dafür, dass obj [Symbol.iterator] etwas anderes als eine (undefinierte oder) Funktion ist. Wenn jemand zum Beispiel String dort platziert, ist es eine schlechte Sache und IMO ist es gut, wenn der Code so schnell wie möglich fehlschlägt.
Tomas Kulich
Würde typeof Object(obj)[Symbol.iterator] === 'function'in allen Fällen funktionieren?
Craig Gidney
26

Warum so ausführlich?

const isIterable = object =>
  object != null && typeof object[Symbol.iterator] === 'function'
Adius
quelle
56
Readability > Clevernesskehrt immer zurück true.
jfmercer
26
Haha, normalerweise bin ich derjenige, der sich über unlesbaren Code beschwert, aber eigentlich denke ich, dass dies ziemlich lesbar ist. Wie ein englischer Satz: Wenn das Objekt nicht null ist und die symbolische Iterator-Eigenschaft eine Funktion ist, ist es iterierbar. Wenn das nicht ganz einfach ist, weiß ich nicht, was ist ...
Adius
4
Imo, der Punkt der "Lesbarkeit" ist zu verstehen, was ohne tatsächliches Lesen passiert
Dmitry Parzhitsky
2
@ Alexander Mills Ihre Verbesserung hat den Code verschlechtert. 1. Wie @jfmercer sagte Readability > Cleverness, hilft es niemandem , die Variable objectzu verkürzen o. 2. Eine leere Zeichenfolge ''ist iterierbar und muss daher zurückgegeben werden true.
Adius
1
Verstanden, ich dachte, es gibt einen Grund, warum es verwendet wurde!= null
Alexander Mills
22

Die einfachste Lösung ist tatsächlich folgende:

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

Objectwird alles, was kein Objekt ist, in ein Objekt einschließen, sodass der inBediener auch dann arbeiten kann, wenn der ursprüngliche Wert kein Objekt ist. nullund undefinedwerden in leere Objekte umgewandelt, sodass keine Erkennung von Kantenfällen erforderlich ist, und Zeichenfolgen werden in iterierbare Zeichenfolgenobjekte eingeschlossen.

Domino
quelle
1
Sie haben das falsche Symbol verwendet. Es ist : Symbol.iterator.
Gil
@ Gil Du hast absolut Recht, oops! Ich hätte getesteten Code kopieren und einfügen sollen, anstatt ihn direkt in einen Beitrag einzugeben.
Domino
Das Erstellen eines neuen Objekts mit Objectist für diese Art der Prüfung verschwenderisch.
Ruben Verborgh
Ich kann nicht sagen, dass ich genau weiß, wie die ObjectFunktion implementiert ist, aber sie erstellt nur dann ein neues Objekt, wenn valuees noch kein Objekt ist. Ich würde erwarten, dass die Kombination von inund Object(...)etwas ist, das Browser-Engines im Gegensatz zu beispielsweise leicht optimieren können value !== undefined && value !== null && value[Symbol.iterator] && true. Außerdem ist es sehr gut lesbar, was mir wichtig ist.
Domino
9

Achten Sie als Nebenbemerkung auf die Definition von iterable . Wenn Sie aus anderen Sprachen kommen, würden Sie erwarten, dass etwas, mit dem Sie beispielsweise eine forSchleife durchlaufen können, iterierbar ist . Ich fürchte, das ist hier nicht der Fall, wo iterable etwas bedeutet, das das Iterationsprotokoll implementiert .

Zur Verdeutlichung kehren alle obigen Beispiele falsezu diesem Objekt zurück, {a: 1, b: 2}da dieses Objekt das Iterationsprotokoll nicht implementiert. Sie werden also nicht in der Lage sein, mit einem for...of ABER darüber zu iterieren, aber Sie können es immer noch mit einem for...in.

Wenn Sie also schmerzhafte Fehler vermeiden möchten, machen Sie Ihren Code spezifischer, indem Sie Ihre Methode wie folgt umbenennen:

/**
 * @param variable
 * @returns {boolean}
 */
const hasIterationProtocol = variable =>
    variable !== null && Symbol.iterator in Object(variable);
Francesco Casula
quelle
Du machst keinen Sinn. Warum glaubst du, würde es brechen undefined?
Adius
@adius Ich glaube, ich habe fälschlicherweise angenommen, dass Sie dies object !== nullin Ihrer Antwort getan haben, aber Sie tun dies, object != nulldamit es undefinedin diesem speziellen Fall nicht bricht . Ich habe meine Antwort entsprechend aktualisiert.
Francesco Casula
OK, ich verstehe. Übrigens: Ihr Code ist falsch. hasIterationProtocol('')muss zurückkehren true! Wie wäre es, wenn Sie Ihren Code entfernen und einfach den iterierbaren Erklärungsabschnitt verlassen, der das einzige ist, was Ihrer Antwort einen echten Mehrwert / etwas Neues hinzufügt.
Adius
1
Es kehrt zurück true, indem ich einen Teil der alten Antwort entfernte, habe ich beide Funktionen zusammengeführt und den strengen Vergleich vergessen. Jetzt habe ich die ursprüngliche Antwort zurückgesetzt, die gut funktioniert hat.
Francesco Casula
1
Ich hätte nie daran gedacht Object(...), einen schönen Fang zu verwenden. In diesem Fall ist die Nullprüfung jedoch nicht erforderlich.
Domino
2

Heutzutage, wie bereits erwähnt, um zu testen, ob objes iterierbar ist, tun Sie es einfach

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

Historische Antwort (nicht mehr gültig)

Das for..ofKonstrukt ist Teil des ECMASCript 6th Edition Language Specification Draft. Es könnte sich also vor der endgültigen Version ändern.

In diesem Entwurf müssen iterierbare Objekte die Funktion iteratorals Eigenschaft haben.

Sie können überprüfen, ob ein Objekt wie folgt iterierbar ist:

function isIterable(obj){
   if(obj === undefined || obj === null){
      return false;
   }
   return obj.iterator !== undefined;
}
Ortomala Lokni
quelle
7
Die Iterator-Eigenschaft war eine vorübergehende Firefox-Sache. Es ist nicht ES6-konform. siehe: developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Amir Arad
2

Bei asynchronen Iteratoren sollten Sie nach 'Symbol.asyncIterator' anstelle von 'Symbol.iterator' suchen:

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
Kamarey
quelle
1

Wenn Sie tatsächlich überprüfen möchten, ob eine Variable ein Objekt ( {key: value}) oder ein Array ( [value, value]) ist, können Sie Folgendes tun:

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);
}
Seglinglin
quelle