Hvordan kan jeg gennemløbe alle poster i et array med JavaScript?
Jeg troede, det var noget i stil med dette:
forEach(instance in theArray)
Hvor theArray
er mit array, men det ser ud til at være forkert.
TL;DR
for-in
unless you use it with safeguards or are at least aware of why it might bite you.for-of
-loop (kun ES2015+),Array#forEach
(spec
| MDN
) (eller dens slægtninge some
og lignende) (kun ES5+),for
-loop,for-in
med sikkerhedsforanstaltninger.
Men der er masser af mere at udforske, læs videre...JavaScript har en kraftfuld semantik til looping gennem arrays og array-lignende objekter. I've delt svaret op i to dele: Muligheder for ægte arrays og muligheder for ting, der blot er array-lignende, såsom arguments
-objektet, andre iterable-objekter (ES2015+), DOM-samlinger osv.
Jeg vil hurtigt bemærke, at du kan bruge ES2015-indstillingerne nu, selv på ES5-motorer, ved at transpile ES2015 til ES5. Søg efter "ES2015 transpiling" / "ES6 transpiling" for mere ...
Okay, lad os se på vores muligheder:
Du har tre muligheder i ECMAScript 5 ("ES5"), den version, der understøttes mest bredt i øjeblikket, og to yderligere tilføjet i ECMAScript 2015 ("ES2015", "ES6"):
forEach
og relaterede (ES5+)for
-loopfor-in
korrekt.for-of
(brug implicit en iterator) (ES2015+)forEach
og relateredeI ethvert vagt moderne miljø (altså ikke IE8), hvor du har adgang til Array
-funktionerne, der blev tilføjet med ES5 (direkte eller ved hjælp af polyfills), kan du bruge forEach
(spec
| MDN
):
var a = ["a", "b", "c"];
a.forEach(function(entry) {
console.log(entry);
});
forEach
accepterer en callback-funktion og eventuelt en værdi, der skal bruges som this
ved kald af denne callback (ikke brugt ovenfor). Callback-funktionen kaldes for hver post i arrayet i rækkefølge, idet ikke-eksisterende poster i sparsomme arrays springes over. Selv om jeg kun brugte ét argument ovenfor, kaldes callbacken med tre: Værdien af hver post, indekset for den pågældende post og en reference til det array, du itererer over (hvis din funktion ikke allerede har det ved hånden).
Medmindre du'støtter forældede browsere som IE8 (som NetApps viser på lidt over 4% markedsandel i skrivende stund i september 2016), kan du med glæde bruge forEach
i en generel webside uden en shim. Hvis du har brug for at understøtte forældede browsere, er det nemt at shimme/polyfylde forEach
(søg efter "es5 shim" for flere muligheder).
forEach
har den fordel, at du ikke behøver at deklarere indekserings- og værdivariabler i det indeholdende scope, da de leveres som argumenter til iterationsfunktionen, og derfor er pænt scopet til netop denne iteration.
Hvis du er bekymret for de omkostninger, der er forbundet med at foretage et funktionskald for hver arraypost, skal du ikke være det; details.
Derudover er forEach
funktionen "loop through them all", men ES5 definerede flere andre nyttige "work your way through the array and do things" funktioner, herunder:
every
(stopper sløjfen første gang callback'en returnerer false
eller noget falsk)some
(stopper looping første gang callback'en returnerer true
eller noget sandt)filter
(opretter et nyt array, der indeholder elementer, hvor filterfunktionen returnerer true
og udelader dem, hvor den returnerer false
)map
(opretter et nyt array ud fra de værdier, der returneres af callback-funktionen)reduce
(opbygger en værdi ved gentagne gange at kalde callbacken og overdrage tidligere værdier; se specifikationen for detaljerne; nyttig til at summere indholdet af et array og mange andre ting)reduceRight
(ligesom reduce
, men arbejder i faldende i stedet for stigende rækkefølge)for
-loopNogle gange er de gamle metoder de bedste:
var index;
var a = ["a", "b", "c"];
for (index = 0; index < a.length; ++index) {
console.log(a[index]);
}
Hvis arrayets længde ikke ændres i løbet af løkken, og det er i ydelsesfølsom kode (usandsynligt), kan en lidt mere kompliceret version, der griber længden på forhånd, være en tiny smule hurtigere:
var index, len;
var a = ["a", "b", "c"];
for (index = 0, len = a.length; index < len; ++index) {
console.log(a[index]);
}
Og/eller at tælle baglæns:
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 sjældent, at du har brug for at få den sidste smule saft ud af det.
I ES2015 og højere versioner kan du gøre dine indeks- og værdivariabler lokale i for
-loopet:
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 gør det, genskabes ikke kun value
, men også index
for hver loop-iteration, hvilket betyder, at lukninger, der oprettes i loop-kroppen, beholder en reference til index
(og value
), der er oprettet for den specifikke iteration:
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 havde fem divs, ville du få "Index er: 0" hvis du klikkede på den første og "Indeks er: 4" hvis du klikkede på den sidste. Dette virker ikke, hvis du bruger var
i stedet for let
.
for-in
korrekt.Du'vil få folk til at fortælle dig at du skal bruge for-in
, men det'er ikke hvad for-in
er til for. for-in
looper gennem enumerable properties of an object, ikke indeksene i et array. Rækkefølgen er ikke garanteret, heller ikke i ES2015 (ES6). ES2015+ definerer en rækkefølge for objektets egenskaber (via [[OwnPropertyKeys]]
, [[Enumerate]]
og ting, der bruger dem som Object.getOwnPropertyKeys
), men det definerer ikke, at for-in
skal følge denne rækkefølge. (Nærmere oplysninger i dette andet svar.)
De eneste reelle anvendelsesmuligheder for for-in
på et array er:
for-in
til at besøge disse sparsomme array-elementer, hvis du bruger passende sikkerhedsforanstaltninger:
// `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]);
}
}
Bemærk de tre kontroller:
// 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);
});
const a = ["a", "b", "c"];
for (const val of a) {
console.log(val);
}
Under dække, der får en iterator fra arrayet og loops gennem det og får værdierne fra det. Dette har ikke det problem, som brugen af for-in
har, fordi det bruger en iterator, der er defineret af objektet (arrayet), og arrays definerer, at deres iteratorer itererer gennem deres entries (ikke deres egenskaber). I modsætning til for-in
i ES5 er rækkefølgen, i hvilken posterne besøges, den numeriske rækkefølge af deres indekser.
Nogle gange kan du måske ønske at bruge en iterator eksplicit. Det kan du også gøre, selv om det er meget mere besværligt end for-of
. Det ser sådan her ud:
const a = ["a", "b", "c"];
const it = a.values();
let entry;
while (!(entry = it.next()).done) {
console.log(entry.value);
}
Iteratoren er et objekt, der svarer til Iterator-definitionen i specifikationen. Dens next
-metode returnerer et nyt result-objekt hver gang den kaldes. Resultatobjektet har en egenskab, done
, der fortæller os, om den er færdig, og en egenskab value
med værdien for den pågældende iteration. (done
er valgfri, hvis det ville være false
, value
er valgfri, hvis det ville være undefined
).
Betydningen af value
varierer afhængigt af iteratoren; arrays understøtter (mindst) tre funktioner, der returnerer iteratorer:
values()
: Dette er den, jeg brugte ovenfor. Den returnerer en iterator, hvor hver value
er array-elementet for den pågældende iteration ("a"
, "b"
, og "c"
i eksemplet tidligere).keys()
: Returnerer en iterator, hvor hver værdi
er nøglen for den pågældende iteration (så for vores a
ovenfor ville det være "0"
, derefter "1"
, derefter "2"
).entries()
: Returnerer en iterator, hvor hver value
er et array i formen [key, value]
for den pågældende iteration.Ud over ægte arrays er der også array-lignende objekter, der har en length
-egenskab og egenskaber med numeriske navne: NodeList
-instanser, arguments
-objektet osv. Hvordan gennemløber vi deres indhold?
I det mindste nogle, og muligvis de fleste eller endda alle ovenstående fremgangsmåder for array-metoder gælder ofte lige så godt for array-lignende objekter:
Brug forEach
og relaterede (ES5+)
De forskellige funktioner på Array.prototype
er "bevidst generiske" og kan normalt bruges på array-lignende objekter via Function#call
eller Function#apply
. (Se Caveat for host-provided objects i slutningen af dette svar, men det er et sjældent problem).
Lad os antage, at du ønsker at bruge forEach
på en Node
's childNodes
-egenskab. Du ville gøre dette:
Array.prototype.forEach.call(node.childNodes, function(child) {
// Gør noget med child
.
});
Hvis du vil gøre det meget, vil du måske gerne have fat i en kopi af funktionsreferencen til en variabel for at kunne genbruge den, f.eks:
// (Dette er alt sammen formentlig i en eller anden scoping-funktion)
var forEach = Array.prototype.forEach;
// Og så senere...
forEach.call(node.childNodes, function(child) {
// Gør noget med child
});
Brug en simpel for
-loop
En simpel for
-loop gælder naturligvis for array-lignende objekter.
Brug for-in
korrekt
for-in
med de samme sikkerhedsforanstaltninger som med et array bør også fungere med array-lignende objekter; forbeholdet for host-provided objekter i #1 ovenfor kan gælde.
Brug for-of
(brug implicit en iterator) (ES2015+)
for-of
vil bruge den iterator, som objektet leverer (hvis der er nogen); vi må se, hvordan det fungerer med de forskellige array-lignende objekter, især værtsleverede objekter. For eksempel blev specifikationen for NodeList
fra querySelectorAll
opdateret for at understøtte iteration. Specifikationen for HTMLCollection
fra getElementsByTagName
blev ikke ændret.
Brug en iterator eksplicit (ES2015+) Se #4, vi'skal se, hvordan iteratorer spiller ud.
Andre gange ønsker du måske at konvertere et array-lignende objekt til et ægte array. Det er overraskende nemt at gøre det:
Brug slice
metoden for arrays
Vi kan bruge slice
-metoden for arrays, der ligesom de andre metoder nævnt ovenfor er "bevidst generisk" og derfor kan bruges med array-lignende objekter, som her:
Det kan f.eks. ske på denne måde: var trueArray = Array.prototype.slice.call(arrayLikeObject);
Så hvis vi f.eks. ønsker at konvertere en NodeList
til et true array, kan vi gøre dette:
var divs = Array.prototype.sli.call(document.querySelectorAll("div")));
Se Caveat for host-provided objects nedenfor. Bemærk især, at dette vil mislykkes i IE8 og tidligere, som ikke tillader dig at bruge host-provided objekter som this
på denne måde.
Brug spread syntax (...
)
Det er også muligt at bruge ES2015's spread syntax med JavaScript-motorer, der understøtter denne funktion:
var trueArray = [...iterableObject];
Så hvis vi f.eks. ønsker at konvertere en NodeList
til et true array, bliver det med spread syntax ganske kortfattet:
var divs = [...document.querySelectorAll("div")]];
Brug Array.from
(spec) | (MDN)
Array.from
(ES2015+, men let polyfyldt) opretter et array fra et array-lignende objekt, eventuelt ved at sende posterne gennem en mapping-funktion først. Så:
var divs = Array.from(document.querySelectorAll("div")));
Eller hvis du ønsker at få et array af tagnavne for elementerne med en given klasse, kan du bruge mapping-funktionen:
// Pilefunktion (ES2015):
var divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);
// Standardfunktion (da Array.from
kan shimmes):
var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
return element.tagName;
});
Hvis du bruger Array.prototype
-funktioner med host-provided array-lignende objekter (DOM-lister og andre ting, der leveres af browseren i stedet for af JavaScript-motoren), skal du sørge for at teste i dine målmiljøer for at sikre, at det host-provided objekt opfører sig korrekt. De fleste opfører sig korrekt (nu), men det er vigtigt at teste. Årsagen er, at de fleste af de Array.prototype
-metoder, som du sandsynligvis vil bruge, er afhængige af, at det værtsleverede objekt giver et ærligt svar på den abstrakte [[HasProperty]]
operation. I skrivende stund klarer browsere dette meget godt, men i 5.1-specifikationen blev der taget højde for muligheden for, at et objekt, der leveres af værten, måske ikke er ærligt. Det står i §8.6.2, flere afsnit under den store tabel nær begyndelsen af dette afsnit), hvor der står:
Værtsobjekter kan implementere disse interne metoder på enhver måde, medmindre andet er angivet; en mulighed er f.eks. at
[[Get]]
og[[Put]]
for et bestemt værtsobjekt faktisk henter og gemmer egenskabsværdier, men at[[HasProperty]]
altid genererer false. (Jeg kunne ikke finde den tilsvarende ordlyd i ES2015-specifikationen, men det er sikkert stadig tilfældet). Igen, i skrivende stund håndterer de almindelige array-lignende objekter i moderne browsere [f.eks.NodeList
-instanser] håndterer [[HasProperty]]` korrekt, men det er vigtigt at teste).
Note: Dette svar er håbløst forældet. For en mere moderne tilgang kan du se på de metoder, der er tilgængelige for et array. Metoder af interesse kan være:
Standardmetoden til at iterere et array i JavaScript er en vanilla for
-loop:
var length = arr.length,
element = null;
for (var i = 0; i < length; i++) {
element = arr[i];
// Do something with element
}
Bemærk dog, at denne fremgangsmåde kun er god, hvis du har et tæt array, og hvert indeks er besat af et element. Hvis arrayet er sparsomt, kan du løbe ind i performanceproblemer med denne fremgangsmåde, da du vil iterere over en masse indekser, der rigtigt ikke findes i arrayet. I dette tilfælde er en for .. in
-loop måske en bedre idé. Du skal imidlertid bruge passende sikkerhedsforanstaltninger for at sikre, at der kun handles på de ønskede egenskaber i arrayet (dvs. arrayelementerne), da for..in
-loopet også vil blive opregnet i ældre browsere, eller hvis de yderligere egenskaber er defineret som enumerable
.
I ECMAScript 5 vil der være en forEach-metode på array-prototypen, men den understøttes ikke i ældre browsere. Så for at kunne bruge den konsekvent skal du enten have et miljø, der understøtter den (f.eks. Node.js til server side JavaScript), eller bruge en "Polyfill". Polyfill'en til denne funktionalitet er dog triviel, og da den gør koden lettere at læse, er det en god polyfill at inkludere.
Hvis du ønsker at gennemløbe et array, skal du bruge standardløkken for
i tre dele.
for (var i = 0; i < myArray.length; i++) {
var arrayItem = myArray[i];
}
Du kan få nogle optimeringer af ydeevnen ved at cache myArray.length
eller ved at iterere over det baglæns.