Sprawdzanie, czy coś jest iterowalne


105

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

for...ofKonstrukcja jest opisana w stanie iteracyjnego „Iterable” obiektów. Ale czy istnieje dobry sposób decydowania, czy obiekt jest iterowalny?

Próbowałem znaleźć wspólne właściwości dla tablic, iteratorów i generatorów, ale nie mogłem tego zrobić.

Pomijając wykonanie for ... ofbloku try i sprawdzanie błędów typu, czy jest na to czysty sposób?


Z pewnością jako autor wiesz, czy Twój obiekt jest iterowalny?
andrewb

6
Obiekt jest przekazywany jako argument, nie jestem pewien.
simonzack

1
Dlaczego nie przetestować typeof argumentu?
James Bruckner,

2
@ andrew-buchan, James Bruckner: Sprawdzanie typów może działać, ale jeśli przeczytasz dokumentację MDN, zauważysz, że jest napisane „podobne do tablicy”. Nie wiem, co to dokładnie oznacza, stąd pytanie.
simonzack,

1
wiki.ecmascript.org/doku.php?id=harmony:iterators stwierdza: „ Obiekt jest iterowalny, jeśli ma iterator()metodę. ”. Ponieważ jednak jest to szkic, tylko czek może zależeć od realizacji. Z jakiego środowiska korzystasz?
Bergi

Odpowiedzi:


144

Właściwy sposób sprawdzenia iterowalności jest następujący:

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

Dlaczego to działa (dogłębnie iterowalny protokół): https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols

Ponieważ mówimy o for..of, zakładam, że jesteśmy nastawieni na ES6.

Nie zdziw się również, że ta funkcja zwraca, truejeśli objjest łańcuchem, ponieważ ciągi iterują po swoich znakach.


15
lub Symbol.iterator in Object(obj),.

14
Istnieje (co najmniej) jeden wyjątek od używania operatora „in”: łańcuch. Łańcuch jest iterowalny (pod względem for..of), ale nie można na nim użyć „in”. Gdyby nie to, wolałbym użyć „in”, to wygląda zdecydowanie ładniej.
Tomas Kulich

nie powinno być return typeof obj[Symbol.iterator] === 'function'? „Aby obiekt był iterowalny, musi implementować metodę @@ iterator” - określa metodę
callum

Nie ma właściwej semantyki dla obj [Symbol.iterator] będącego czymś innym niż (undefined lub) funkcją. Jeśli ktoś umieści tam np. String, to źle, a IMO dobrze, jeśli kod zawiedzie tak szybko, jak to możliwe.
Tomas Kulich

Czy typeof Object(obj)[Symbol.iterator] === 'function'zadziała we wszystkich przypadkach?
Craig Gidney

25

Dlaczego taki gadatliwy?

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

57
Readability > Clevernesszawsze wraca true.
jfmercer

29
Haha, zwykle to ja narzekam na nieczytelny kod, ale tak naprawdę myślę, że jest to całkiem czytelne. Jak angielskie zdanie: jeśli obiekt nie jest pusty, a symboliczna właściwość iteratora jest funkcją, to jest iterowalna. Jeśli to nie jest śmiertelnie proste, nie wiem, co to jest ...
adius

4
imo, celem „czytelności” jest zrozumienie, co się dzieje bez faktycznego czytania
Dima Parzhitsky

2
@Alexander Mills Twoje ulepszenie pogorszyło kod. 1. Jak powiedział @jfmercer Readability > Cleverness, więc skrócenie zmiennej objectdo onikogo nie pomoże. 2. Pusty ciąg ''jest iterowalny i dlatego musi powrócić true.
adius

2
Być może jedyną rzeczą, którą trzeba czytać to nazwa: isIterable.
Matthias

22

Najprostsze rozwiązanie jest w rzeczywistości takie:

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

Objectzawinie wszystko, co nie jest obiektem, w jeden, umożliwiając inoperatorowi pracę, nawet jeśli oryginalna wartość nie jest obiektem. nulli undefinedsą zamieniane na puste obiekty, więc nie ma potrzeby wykrywania wielkości liter, a łańcuchy są zawijane w obiekty typu String, które są iterowalne.


1
Użyłeś niewłaściwego symbolu. To: Symbol.iterator.
Gil

@Gil Masz całkowitą rację, oops! Powinienem był skopiować przetestowany kod zamiast pisać bezpośrednio w poście.
Domino

Tworzenie nowego obiektu Objectjest marnotrawstwem dla tego typu kontroli.
Ruben Verborgh

Nie mogę powiedzieć, że wiem dokładnie, jak Objectfunkcja jest zaimplementowana, ale tworzy nowy obiekt tylko wtedy, gdy valuenie jest już obiektem. Spodziewałbym się, że to połączenie ini Object(...)będzie czymś, co silniki przeglądarek mogą łatwo zoptymalizować, w przeciwieństwie do tego, co mówimy value !== undefined && value !== null && value[Symbol.iterator] && true. Poza tym jest niezwykle czytelny, na czym mi zależy.
Domino

9

Na marginesie, UWAŻAJ na definicję iterowalności . Jeśli pochodzisz z innych języków, spodziewasz się, że coś, po czym możesz iterować, powiedzmy, forpętla jest iterowalna . Obawiam się, że tak nie jest w tym przypadku, gdy iterowalność oznacza coś, co implementuje protokół iteracji .

Aby wszystko było jaśniejsze, wszystkie powyższe przykłady zwracają falsesię do tego obiektu, {a: 1, b: 2}ponieważ ten obiekt nie implementuje protokołu iteracji. Więc nie będziesz w stanie go iterować za pomocą for...of ALE nadal możesz z for...in.

Jeśli więc chcesz uniknąć bolesnych błędów, doprecyzuj swój kod, zmieniając nazwę metody, jak pokazano poniżej:

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

Nie masz sensu. Jak myślisz, dlaczego to by się zepsuło undefined?
adius

@adius Myślę, że błędnie założyłem, że robiłeś to object !== nullw swojej odpowiedzi, ale robisz, object != nullwięc undefinedw tym konkretnym przypadku nie zrywa . Odpowiednio zaktualizowałem moją odpowiedź.
Francesco Casula

Dobra, widzę. Btw: Twój kod jest nieprawidłowy. hasIterationProtocol('')musi wrócić true! Co powiesz na usunięcie kodu i po prostu opuszczenie iterowalnej sekcji wyjaśnień, która jest jedyną rzeczą, która dodaje prawdziwą wartość / coś nowego w Twojej odpowiedzi.
adius

1
Wraca true, usuwając część starej odpowiedzi, połączyłem obie funkcje i zapomniałem o ścisłym porównaniu. Teraz odłożyłem oryginalną odpowiedź, która działała dobrze.
Francesco Casula

1
Nigdy bym nie pomyślał o użyciu Object(...), niezłego haczyka. Ale w takim przypadku sprawdzenie zerowe nie jest potrzebne.
Domino

3

W przypadku iteratorów asynchronicznych należy sprawdzić „Symbol.asyncIterator” zamiast „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

2

W dzisiejszych czasach, jak już wspomniano, aby sprawdzić, czy objjest iterowalne, po prostu zrób

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

Historyczna odpowiedź (już nieaktualna)

Plik for..ofKonstrukt jest częścią ECMAScript 6. wydanie Language Specification Draft. Więc może się to zmienić przed ostateczną wersją.

W tej wersji roboczej iterowalne obiekty muszą mieć funkcjęiterator jako właściwość.

Możesz sprawdzić, czy obiekt jest iterowalny w następujący sposób:

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

7
Właściwość iteratora była tymczasową rzeczą w firefoxie. Nie jest zgodny z ES6. zobacz: developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Amir Arad

1

Jeśli chcesz sprawdzić, czy zmienna jest obiektem ( {key: value}) czy tablicą ( [value, value]), możesz to zrobić:

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);
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.