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.
TL;DR
for-in
med mindre du bruker det med garantier eller i det minste er klar over hvorfor det kan bite deg.for-of
løkke (kun ES2015+),Array#forEach
(spec
| MDN
) (eller dens slektninger some
og lignende) (kun ES5+),for
-sløyfe,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 nå , 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:
Du har tre alternativer i ECMAScript 5 ("ES5"), den versjonen som støttes mest for øyeblikket, og to til lagt til i ECMAScript 2015 ("ES2015", "ES6"):
forEach
og relaterte (ES5+).for
-løkkefor-in
korrekt.for-of
(bruk en iterator implisitt) (ES2015+)forEach
og relaterteI 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
| MDN
):
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
(slutter å løkke første gang callback returnerer false
eller noe falskt)some
(slutter å løkke første gang tilbakekallingen returnerer true
eller noe sant)filter
(oppretter en ny matrise som inkluderer elementer der filterfunksjonen returnerer true
og utelater de der den returnerer false
)map
(oppretter en ny matrise fra verdiene som returneres av tilbakekallingen)reduce
(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
(som reduce
, men fungerer i synkende i stedet for stigende rekkefølge)for
løkkeNoen 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
.
for-in
korrekt.Du vil få folk til å be deg om å bruke for-in
, men det er ikke det for-in
er til for. 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
), men den definerer ikke at for-in
vil følge denne rekkefølgen. (Detaljer i dette andre svaret).
De eneste virkelige brukstilfellene for for-in
på en matrise er:
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 <= 4294967294 // below
) {
console.log(a[key]);
}
}
Legg merke til de tre kontrollene:
// Utility function for antiquated environments without `forEach`
var hasOwn = Object.prototype.hasOwnProperty;
var rexNum = /^0$|^[1-9]\d*$/;
function sparseEach(array, callback, thisArg) {
var index;
for (var key in array) {
index = +key;
if (hasOwn.call(a, key) &&
rexNum.test(key) &&
index <= 4294967294
) {
callback.call(thisArg, array[key], index, array);
}
}
}
var a = [];
a[5] = "five";
a[10] = "ten";
a[100000] = "one hundred thousand";
a.b = "bee";
sparseEach(a, function(value, index) {
console.log("Value at " + index + " is " + value);
});
for-of
(bruk en iterator implisitt) (ES2015+)ES2015 legger til iteratorer i JavaScript. Den enkleste måten å bruke iteratorer på er den nye for-of
-setningen. Den ser slik ut:
const a = ["a", "b", "c"];
for (const val of a) {
console.log(val);
}
Under dekslene, som får en iterator fra arrayet og løkker gjennom det, og får verdiene fra det. Dette har ikke problemet som bruk av for-in
har, fordi den bruker en iterator definert av objektet (arrayet), og matriser definerer at iteratorene deres itererer gjennom oppføringene (ikke egenskapene deres). I motsetning til for-in
i ES5 er rekkefølgen som oppføringene besøkes i, den numeriske rekkefølgen på indeksene.
Noen ganger vil du kanskje bruke en iterator eksplisitt. Du kan gjøre det også, selv om det er mye mer klumpete enn for-of
. Det ser ut som dette:
const a = ["a", "b", "c"];
const it = a.values();
let entry;
while (!(entry = it.next()).done) {
console.log(entry.value);
}
Iteratoren er et objekt som samsvarer med Iterator-definisjonen i spesifikasjonen. Dens next
-metode returnerer et nytt resultatobjekt hver gang du kaller den. Resultatobjektet har en egenskap, done
, som forteller oss om det er ferdig, og en egenskap value
med verdien for den iterasjonen. (done
er valgfritt hvis det skulle være false
, value
er valgfritt hvis det skulle være undefined
).
Betydningen av value
varierer avhengig av iteratoren; matriser støtter (minst) tre funksjoner som returnerer iteratorer:
values()
: Dette er den jeg brukte ovenfor. Den returnerer en iterator der hver verdi
er arrayoppføringen for den iterasjonen ("a"
, "b"
og "c"
i eksemplet tidligere).nøkler()
: Returnerer en iterator der hver verdi
er nøkkelen for den iterasjonen (så for vår a
ovenfor vil det være "0"
, deretter "1"
, deretter "2"
).entries()
: Returnerer en iterator der hver verdi
er en matrise på formen [nøkkel, verdi]
for den iterasjonen.Bortsett fra ekte arrays, finnes det også array-lignende objekter som har en lengde
-egenskap og egenskaper med numeriske navn: NodeList
-instanser, arguments
-objektet osv. Hvordan løkker vi gjennom innholdet deres?
I det minste noen, og muligens de fleste eller til og med alle, av array-tilnærmingene ovenfor gjelder ofte like godt for array-lignende objekter:
Bruk forEach
og beslektede (ES5+).
De forskjellige funksjonene på Array.prototype
er "tilsiktet generiske" og kan vanligvis brukes på array-lignende objekter via Function#call
eller Function#apply
. (Se Caveat for host-provided objects på slutten av dette svaret, men det er et sjeldent problem).
Anta at du ønsket å bruke forEach
på en Node
childNodes
egenskap. Du'd gjøre dette:
Array.prototype.forEach.call(node.childNodes, function(child) {
// Gjør noe med child
});
Hvis du' kommer til å gjøre det mye, kan det være lurt å ta en kopi av funksjonsreferansen i en variabel for gjenbruk, for eksempel:
// (Dette er alt antagelig i en eller annen scoping-funksjon)
var forEach = Array.prototype.forEach;
// Så senere...
forEach.call(node.childNodes, function(child) {
// Gjør noe med child
});
Bruk en enkel for
løkke.
Åpenbart gjelder en enkel for
løkke for array-lignende objekter.
Bruk for-in
korrekt.
for-in
med de samme sikkerhetstiltakene som med en matrise bør også fungere med matriselignende objekter; forbeholdet for vertsleverte objekter i punkt 1 ovenfor kan gjelde.
Bruk for-of
(bruk en iterator implisitt) (ES2015+).
for-of
vil bruke iteratoren som tilbys av objektet (hvis noen); vi må se hvordan dette fungerer med de ulike array-lignende objektene, spesielt de som tilbys av verten. For eksempel ble spesifikasjonen for NodeList
fra querySelectorAll
oppdatert for å støtte iterasjon. Spesifikasjonen for HTMLCollection
fra getElementsByTagName
ble ikke oppdatert.
Bruk en iterator eksplisitt (ES2015+). Se nr. 4, vi må se hvordan iteratorer fungerer.
Andre ganger vil du kanskje konvertere et array-lignende objekt til et ekte array. Å gjøre det er overraskende enkelt:
Bruk slice
metoden for arrays *.
Vi kan bruke slice
-metoden for matriser, som i likhet med de andre metodene nevnt ovenfor er " med vilje generisk " og så kan brukes med array-lignende objekter, som dette:
var trueArray = Array.prototype.slice.call(arrayLikeObject);
Så for eksempel, hvis vi ønsker å konvertere en NodeList
til en ekte array, kan vi gjøre dette:
var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
Se Caveat for host-provided objects* nedenfor. Vær spesielt oppmerksom på at dette vil mislykkes i IE8 og tidligere, som ikke lar deg bruke vertsleverte objekter som "dette" slik.
Bruk spread syntax (...
).
Det er også mulig å bruke ES2015s spread syntax med JavaScript-motorer som støtter denne funksjonen:
var trueArray = [...iterableObject];
Så hvis vi for eksempel ønsker å konvertere en NodeList
til en true array, blir dette ganske kortfattet med spread syntaks:
var divs = [...document.querySelectorAll("div")];
Bruk Array.from
(spec) | (MDN) Bruk Array.from
** (spec) | (MDN)
Array.from
(ES2015+, men lett å polyfylle) oppretter en matrise fra et matriselignende objekt, og sender eventuelt oppføringene gjennom en tilordningsfunksjon først. Så:
var divs = Array.from(document.querySelectorAll("div"));
Eller hvis du ønsker å få et array av tagnavnene til elementene med en gitt klasse, bruker du tilordningsfunksjonen:
// Arrow-funksjonen (ES2015):
var divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);
// Standardfunksjon (siden Array.from
kan shimmes):
var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
return element.tagName;
});
Hvis du bruker Array.prototype
-funksjoner med host-provided array-lignende objekter (DOM-lister og andre ting som leveres av nettleseren i stedet for JavaScript-motoren), må du sørge for å teste i målmiljøene dine for å sikre at host-provided-objektet oppfører seg riktig. De fleste oppfører seg riktig (nå), men det er viktig å teste. Årsaken er at de fleste Array.prototype
-metodene du sannsynligvis vil bruke, er avhengige av at det vertsleverte objektet gir et ærlig svar på den abstrakte [[HasProperty]]
-operasjonen. I skrivende stund gjør nettlesere en veldig god jobb med dette, men 5.1-spesifikasjonen tillot muligheten for at et vertsprodusert objekt kanskje ikke er ærlig. Det er i §8.6.2, flere avsnitt under den store tabellen nær begynnelsen av den delen), der det står:
Vertsobjekter kan implementere disse interne metodene på hvilken som helst måte med mindre annet er spesifisert; for eksempel er en mulighet at
[[Get]]
og[[Put]]
for et bestemt vertsobjekt faktisk henter og lagrer egenskapsverdier, men[[HasProperty]]
genererer alltid false. (Jeg kunne ikke finne den tilsvarende ordbruken i ES2015-spesifikasjonen, men det er sikkert fortsatt tilfelle). Igjen, i skrivende stund håndterer de vanlige vertsleverte array-lignende objektene i moderne nettlesere [NodeList
-instanser, for eksempel] håndterer [[HasProperty]]` riktig, men det er viktig å teste).
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:
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.
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.