javascript - Sjekke om noe er itererbart


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

for...of-konstruksjonen beskrives å kunne iterere over "iterable" objekter. Men finnes det en god måte å avgjøre om et objekt er iterabelt?

Jeg har forsøkt å finne felles egenskaper for arrays, iteratorer og generatorer, men har ikke klart å gjøre det.

Bortsett fra å gjøre en for ... of i en try-blokk og sjekke for typefeil, finnes det en enkel måte å gjøre dette på?

Svar #1

Den riktige måten å sjekke for itererbarhet på er som følger:

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

Hvorfor dette fungerer (itererbar protokoll i detalj): https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols

Siden vi snakker om for..of, antar jeg at vi har et ES6-sinnelag.

Ikke bli overrasket over at denne funksjonen returnerer true hvis obj er en streng, siden strenger itererer over sine tegn.

Svar #2

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

}

Object vil pakke alt som ikke er et objekt inn i ett. Dette forhindrer at in-operatoren kaster unntak og fjerner behovet for å sjekke for kanttilfeller. null og undefined blir til tomme objekter, og betingelsen evalueres til true for en streng, array, Map, Set, og så videre.

Alternativt, i et miljø som støtter valgfri kjeding:

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

}

Syntaksen ?. gjør at null og undefined blir ignorert, og egenskapstilgang er trygg på alle andre verdityper. Dette kan hende ikke leses som engelsk, men å sjekke sannhetsverdien til en egenskap for å hevde dens eksistens er ganske idiomatisk.

Jeg vil personlig anbefale å inline denne koden i stedet for å definere en funksjon, siden den er relativt kort og mindre tvetydig enn et kall til en funksjon som isIterable(x).


Husk at ordet iterable spesifikt refererer til det iterable protokoll, selv om det finnes forskjellige måter å iterere over objekter på. Den følgende listen er ikke uttømmende:

  • arr.forEach(func) kjører en funksjon på hver ikke-tomme plass i et array (se sparsomme arrays her).
  • Du kan oppliste visse nøkler i et objekt med for ... in
  • Noen samlinger har .keys(), .values() og .entries() metoder som returnerer iteratorer.
  • Object.keys(obj), Object.values(obj) eller Object.entries(obj) returnerer arrays laget fra et objekts egenskaper, som du deretter kan iterere over.

Svar #3

Hvorfor så ordrik?

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

Svar #4

2022 Svar

Hvis du spør "Er foo itererbar" kommer du sannsynligvis fra et språk (PHP, Python) hvor det spørsmålet har ett enkelt svar. I moderne Javascript finnes det forskjellige typer itererbare. Derfor må du sjekke evnen til å iterere avhengig av hva du vil gjøre med variabelen.

Kort fortalt

  • Test evnen til å iterere ved bruk av forEach() med !!foo.forEach, returnerer true på en Array.
  • Test evnen til å iterere ved bruk av for..of med !!foo[Symbol.iterator], returnerer true på en Array eller String.
  • Test evnen til å iterere ved bruk av for..in med !!Object.keys(Object(foo)).length, returnerer sant på en Array, String eller Object.

Langt svar

La oss definere noen 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"}

Tester itererbarhet med forEach()

Hvilke typer kan itereres med forEach(), testet 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

Bare Array ser ut til å være itererbar med forEach().

Testing av itererbarhet med for..of

Hvilke typer kan itereres med for..of, testet 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 og String ser ut til å være iterable med for..of, men Object er det ikke. Og både Number og Object ga en feil.

Testing av itererbarhet med for..in

Hvilke typer kan itereres med for..in, testes 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 og Object ser alle ut til å være iterable med for..in. Og selv om det ikke itererte, kastet ikke Number en feil.

I moderne ES6 Javascript ser jeg forEach brukt mye oftere enn for..in eller for..of. Men Javascript-utviklere må være klar over forskjellene mellom de tre tilnærmingene, og den forskjellige oppførselen til hver tilnærming.

Svar #5

Som en merknad, VÆR oppmerksom på definisjonen av iterable. Hvis du kommer fra andre språk, vil du forvente at noe du kan iterere over med for eksempel en for-løkke er iterable. Jeg er redd det ikke er tilfelle her, hvor iterable betyr noe som implementerer iterasjonsprotokollen.

For å gjøre ting klarere returnerer alle eksemplene ovenfor false på dette objektet {a: 1, b: 2} fordi dette objektet ikke implementerer iterasjonsprotokollen. Så du vil ikke kunne iterere over det med en for...of MEN du kan fortsatt gjøre det med en for...in.

Så hvis du vil unngå smertefulle feil, gjør koden din mer spesifikk ved å gi metoden din et nytt navn som vist nedenfor:

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

Relaterte artikler