javascript - Kontrollera om något är itererbart


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

Konstruktionen for...of beskrivs som att kunna iterera över "itererbara" objekt. Men finns det ett bra sätt att avgöra om ett objekt är itererbart?

Jag har försökt hitta gemensamma egenskaper för arrayer, iteratorer och generatorer, men har inte lyckats göra det.

Förutom att göra en for ... of i ett try-block och kontrollera typfel, finns det ett rent sätt att göra detta?

Svar #1

Det korrekta sättet att kontrollera om något är itererbart är som följer:

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

Varför detta fungerar (itererbart protokoll på djupet): https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols

Eftersom vi pratar om for..of antar jag att vi har ES6-tänkesättet.

Bli heller inte förvånad om denna funktion returnerar true om obj är en sträng, eftersom strängar itererar över sina tecken.

Svar #2

if (Symbol.iterator in Object(value)) {

}

Object kommer att kapsla in allt som inte är ett objekt till ett. Detta förhindrar att in-operatorn kastar undantag och tar bort behovet av att kontrollera för kantfall. null och undefined blir tomma objekt, och villkoret utvärderas till true för en sträng, array, Map, Set, och så vidare.

Alternativt, i en miljö som stödjer valfri kedjning:

if (value?.[Symbol.iterator]) {

}

Syntaxen ?. gör att null och undefined ignoreras, och egenskapsåtkomst är säker på alla andra värdetyper. Detta kanske inte låter som engelska, men att kontrollera sanningsvärdet av en egenskap för att fastställa dess existens är ganska idiomatiskt.

Jag skulle personligen rekommendera att inline den här koden istället för att definiera en funktion, eftersom den är relativt kort och mindre tvetydig än ett anrop till någon isIterable(x)-funktion.


Tänk på att ordet iterable specifikt hänvisar till det iterabla protokollet, även om det finns olika sätt att loopa över objekt. Följande lista är inte uttömmande:

  • arr.forEach(func) kör en funktion på varje icke-tomt element i en array (se glest befolkade arrayer här).
  • Du kan räkna upp vissa nycklar i ett objekt med for ... in
  • Vissa samlingar har .keys(), .values() och .entries() metoder som returnerar iteratorer.
  • Object.keys(obj), Object.values(obj) eller Object.entries(obj) returnerar arrayer skapade från ett objekts egenskaper, som du sedan kan iterera över.

Svar #3

Varför så ordrik?

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

Svar #4

2022 Svar

Om du frågar "Är foo itererbar" kommer du förmodligen från ett språk (PHP, Python) där den frågan har ett enda svar. I modern Javascript finns det olika typer av itererbara objekt. Därför måste du kontrollera möjligheten att iterera beroende på vad du vill göra med variabeln.

Sammanfattning

  • Testa förmågan att iterera med forEach() med !!foo.forEach, returnerar sant på en Array.
  • Testa förmågan att iterera med for..of med !!foo[Symbol.iterator], returnerar sant på en Array eller Sträng.
  • Testa förmågan att iterera med for..in med !!Object.keys(Object(foo)).length, returnerar sant på en Array, String eller Object.

Långt svar

Låt oss definiera några variabler:

const someNumber = 42;
42

const someArray = [1,2,3];
(3) [1, 2, 3]

const someString = "Hello";
"Hello, world!"

const someObject = {a:"A", b:"B"};
{a: "A", b: "B"}

Testa itererbarhet med forEach()

Vilka typer kan itereras med forEach(), testade med !!foo.forEach:

someNumber.forEach(x=>console.log(x));
VM1526:1 Uncaught TypeError: someNumber.forEach is not a function at <anonymous>:1:12

someArray.forEach(x=>console.log(x));
VM916:1 1
VM916:1 2
VM916:1 3
undefined

someString.forEach(x=>console.log(x));
VM957:1 Uncaught TypeError: someString.forEach is not a function at <anonymous>:1:12

someObject.forEach(x=>console.log(x));
VM994:1 Uncaught TypeError: someObject.forEach is not a function at <anonymous>:1:12

Endast Array verkar vara itererbar med forEach().

Testar itererbarhet med for..of

Vilka typer kan itereras med for..of, testade med !!foo[Symbol.iterator]:

for (x of someNumber) { console.log(x); }
VM21027:1 Uncaught TypeError: someNumber is not iterable at <anonymous>:1:11
    
for (x of someArray) { console.log(x); }
VM21047:1 1
VM21047:1 2
VM21047:1 3
undefined

for (x of someString) { console.log(x); }
VM21065:1 H
VM21065:1 e
VM21065:1 l
VM21065:1 l
VM21065:1 o
undefined

for (x of someObject) { console.log(x); }
VM21085:1 Uncaught TypeError: someObject is not iterable at <anonymous>:1:11

Array och String verkar vara itererbara med for..of, men Object är det inte. Och både Number och Object kastade ett fel.

Testar itererbarhet med for..in

Vilka typer kan itereras med for..in, testas med !!Object.keys(Object(foo)).length:

for (x in someNumber) { console.log(x); }
undefined

for (x in someArray) { console.log(x); }
VM20918:1 0
VM20918:1 1
VM20918:1 2
undefined

for (x in someString) { console.log(x); }
VM20945:1 0
VM20945:1 1
VM20945:1 2
VM20945:1 3
VM20945:1 4
undefined

for (x in someObject) { console.log(x); }
VM20972:1 a
VM20972:1 b
undefined

Array, String och Object verkar alla vara itererbara med for..in. Och även om det inte itererade, kastade Number inget fel.

I modern ES6 Javascript ser jag forEach användas mycket oftare än for..in eller for..of. Men Javascript-utvecklare måste vara medvetna om skillnaderna mellan de tre metoderna och det olika beteendet hos varje metod.

Svar #5

Som en bisats, VARNA om definitionen av iterable. Om du kommer från andra språk skulle du förvänta dig att något som du kan iterera över med, säg, en for-loop är iterable. Jag är rädd att det inte är fallet här där iterable betyder något som implementerar iterationsprotokollet.

För att göra saker klarare returnerar alla exempel ovan false på detta objekt {a: 1, b: 2} eftersom det objektet inte implementerar itereringsprotokollet. Så du kommer inte kunna iterera över det med en for...of MEN du kan fortfarande göra det med en for...in.

Så om du vill undvika smärtsamma misstag, gör din kod mer specifik genom att byta namn på din metod som visas nedan:

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

Relaterade artiklar