For-each over en matrise i JavaScript?

Hvordan kan jeg løkke gjennom alle oppføringene i en matrise ved hjelp av JavaScript?

Jeg trodde det var noe sånt som dette:

forEach(instance in theArray)

Hvor theArray er matrisen min, men dette ser ut til å være feil.

Løsning

TL;DR

  • Ikke bruk for-in med mindre du bruker det med garantier eller i det minste er klar over hvorfor det kan bite deg.
  • De beste alternativene er vanligvis
  • en for-of løkke (kun ES2015+),
  • Array#forEach ([spec][1] | [MDN][2]) (eller dens slektninger some og lignende) (kun ES5+),
  • en enkel gammeldags for-sløyfe,
  • eller for-in med sikkerhetstiltak. Men det er mye mer å utforske, les videre...

    JavaScript har kraftig semantikk for looping gjennom matriser og array-lignende objekter. Jeg har delt svaret i to deler: Alternativer for ekte arrays, og alternativer for ting som er bare array-like, for eksempel arguments objekt, andre iterable objekter (ES2015+), DOM samlinger, og så videre. Jeg vil raskt merke at du kan bruke ES2015-alternativene , selv på ES5-motorer, ved å overføre ES2015 til ES5. Søk etter "ES2015 transpiling" / "ES6 transpiling" for mer ... Ok, la oss se på alternativene våre:

    For faktiske matriser

    Du har tre alternativer i [ECMAScript 5][3] ("ES5"), den versjonen som støttes mest for øyeblikket, og to til lagt til i [ECMAScript 2015][4] ("ES2015", "ES6"):

  1. Bruk forEach og relaterte (ES5+).
  2. Bruk en enkel for-løkke
  3. Bruk for-in korrekt.
  4. Bruk for-of (bruk en iterator implisitt) (ES2015+)
  5. Bruke en iterator eksplisitt (ES2015+). Detaljer:

    1. Bruk forEach og relaterte

    I ethvert vagt moderne miljø (altså ikke IE8) der du har tilgang til Array-funksjonene som er lagt til av ES5 (direkte eller ved hjelp av polyfills), kan du bruke forEach ([spec][1] | [MDN][2]):

var a = ["a", "b", "c"];
a.forEach(function(entry) {
    console.log(entry);
});

forEach aksepterer en tilbakeringingsfunksjon og eventuelt en verdi som skal brukes som this når tilbakeringingen kalles (ikke brukt ovenfor). Tilbakekallingen kalles for hver oppføring i matrisen, i rekkefølge, og hopper over ikke-eksisterende oppføringer i sparsomme matriser. Selv om jeg bare brukte ett argument ovenfor, kalles tilbakeringingen med tre: Verdien av hver oppføring, indeksen for den oppføringen, og en referanse til matrisen du itererer over (i tilfelle funksjonen din ikke allerede har den hendig). Med mindre du støtter foreldede nettlesere som IE8 (som NetApps viser til litt over 4% markedsandel i skrivende stund i september 2016), kan du gjerne bruke forEach på en generell webside uten mellomlegg. Hvis du trenger å støtte foreldede nettlesere, er det enkelt å shimme/polyfille forEach (søk etter "es5 shim" for flere alternativer). forEach har den fordelen at du ikke trenger å erklære indeksering og verdivariabler i det inneholdende omfanget, ettersom de leveres som argumenter til iterasjonsfunksjonen, og så pent avgrenset til bare den iterasjonen. Hvis du're bekymret for kjøretid kostnadene ved å gjøre et funksjonskall for hver array oppføring, ikke være; detaljer. I tillegg er forEach funksjonen "loop through them all", men ES5 definerte flere andre nyttige "work your way through the array and do things" -funksjoner, inkludert:

  • [every][5] (slutter å løkke første gang callback returnerer false eller noe falskt)
  • [some][6] (slutter å løkke første gang tilbakekallingen returnerer true eller noe sant)
  • [filter][7] (oppretter en ny matrise som inkluderer elementer der filterfunksjonen returnerer true og utelater de der den returnerer false)
  • [map][8] (oppretter en ny matrise fra verdiene som returneres av tilbakekallingen)
  • [reduce][9] (bygger opp en verdi ved gjentatte ganger å kalle tilbakekallingen og sende inn tidligere verdier; se spesifikasjonen for detaljer; nyttig for å summere innholdet i en matrise og mange andre ting)
  • [reduceRight][10] (som reduce, men fungerer i synkende i stedet for stigende rekkefølge)

    2. Bruk en enkel for løkke

    Noen ganger er de gamle måtene de beste:

var index;
var a = ["a", "b", "c"];
for (index = 0; index < a.length; ++index) {
    console.log(a[index]);
}

Hvis lengden på arrayet ikke endres i løpet av løkken, og det er i ytelsesfølsom kode (usannsynlig), kan en litt mer komplisert versjon som tar tak i lengden foran være litt raskere:

var index, len;
var a = ["a", "b", "c"];
for (index = 0, len = a.length; index < len; ++index) {
    console.log(a[index]);
}

Og/eller teller baklengs:

var index;
var a = ["a", "b", "c"];
for (index = a.length - 1; index >= 0; --index) {
    console.log(a[index]);
}

Men med moderne JavaScript-motorer er det sjelden du trenger å hente ut den siste biten av juice. I ES2015 og nyere kan du gjøre indeks- og verdivariablene dine lokale for for -sløyfen:

let a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
    let value = a[index];
    console.log(index, value);
}
//console.log(index);   // would cause "ReferenceError: index is not defined"
//console.log(value);   // would cause "ReferenceError: value is not defined"
let a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
    let value = a[index];
    console.log(index, value);
}
try {
    console.log(index);
} catch (e) {
    console.error(e);   // "ReferenceError: index is not defined"
}
try {
    console.log(value);
} catch (e) {
    console.error(e);   // "ReferenceError: value is not defined"
}

Og når du gjør det, blir ikke bare value, men også index gjenskapt for hver loop-iterasjon, noe som betyr at lukninger opprettet i loop-kroppen holder en referanse til index (og value) opprettet for den spesifikke iterasjonen:

let divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
    divs[index].addEventListener('click', e => {
        console.log("Index is: " + index);
    });
}
let divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
    divs[index].addEventListener('click', e => {
        console.log("Index is: " + index);
    });
}
<div>zero</div>
<div>one</div>
<div>two</div>
<div>three</div>
<div>four</div>

Hvis du hadde fem divs, ville du få "Index is: 0" hvis du klikket på den første og "Index is: 4 " hvis du klikket på den siste. Dette fungerer ikke hvis du bruker var i stedet for let.

3. Bruk for-in korrekt.

Du vil få folk til å be deg om å bruke for-in, men [det er ikke det for-in er til for][11]. for-in løkker gjennom de oppregnede egenskapene til et objekt, ikke indeksene til et array. Rekkefølgen er ikke garantert, ikke engang i ES2015 (ES6). ES2015+ definerer en rekkefølge for objektegenskaper (via [[OwnPropertyKeys]], [[Enumerate]], og ting som bruker dem som [Object.getOwnPropertyKeys][12]), men den definerer ikke at for-in vil følge denne rekkefølgen. (Detaljer i [dette andre svaret][13]). De eneste virkelige brukstilfellene for for-in på en matrise er:

  • Det er en [sparsom matrise][14] med massive hull i den, eller
  • Du bruker ikke-element-egenskaper, og du vil inkludere dem i løkken Ser bare på det første eksempelet: Du kan bruke for-in for å besøke disse sparese array-elementene hvis du bruker passende sikkerhetstiltak:

// `a` is a sparse array
var key;
var a = [];
a[0] = "a";
a[10] = "b";
a[10000] = "c";
for (key in a) {
    if (a.hasOwnProperty(key)  &&        // These checks are
        /^0$|^[1-9]\d*$/.test(key) &&    // explained
        key 
Kommentarer (24)

Bemerkning: Dette svaret er håpløst utdatert. For en mer moderne tilnærming, se metodene som er tilgjengelige på en matrise. Metoder av interesse kan være:

  • forEach
  • kart
  • filter
  • zip
  • redusere
  • alle
  • noen

Standardmetoden for å iterere et array i JavaScript er en vanilje for-løkke:

var length = arr.length,
    element = null;
for (var i = 0; i < length; i++) {
  element = arr[i];
  // Do something with element
}

Merk imidlertid at denne tilnærmingen bare er bra hvis du har en tett matrise, og hver indeks er okkupert av et element. Hvis matrisen er sparsom, kan du støte på ytelsesproblemer med denne tilnærmingen, siden du vil iterere over mange indekser som ikke egentlig eksisterer i matrisen. I dette tilfellet kan en for .. in-løkke være en bedre idé. **Du må imidlertid bruke passende sikkerhetstiltak for å sikre at bare de ønskede egenskapene til matrisen (det vil si matriseelementene) blir behandlet, siden for..in-løkken også vil bli oppregnet i eldre nettlesere, eller hvis tilleggsegenskapene er definert som enumerable.

I ECMAScript 5 vil det være en forEach-metode på array-prototypen, men den støttes ikke i eldre nettlesere. Så for å kunne bruke den konsekvent må du enten ha et miljø som støtter den (for eksempel Node.js for JavaScript på serversiden), eller bruke en "Polyfill". Polyfill for denne funksjonaliteten er imidlertid triviell, og siden den gjør koden lettere å lese, er det en god polyfill å inkludere.

Kommentarer (4)

Hvis du vil løkke over en matrise, bruker du standard tredelt for-løkke.

for (var i = 0; i < myArray.length; i++) {
    var arrayItem = myArray[i];
}

Du kan få noen ytelsesoptimaliseringer ved å hurtigbufre myArray.length eller iterere over den bakover.

Kommentarer (5)