Hoe kan ik met JavaScript door alle items in een array lopen?
Ik dacht dat het zoiets was als dit:
forEach(instance in theArray)
Waarbij deArray
mijn array is, maar dit lijkt niet te kloppen.
TL;DR
for-in
unless you use it with safeguards or are at least aware of why it might bite you.for-of
lus (alleen ES2015+),Array#forEach
(spec
| MDN
) (of zijn verwanten some
en dergelijke) (alleen ES5+),for
lus,for-in
met beveiligingen.
Maar er is veel meer te ontdekken, lees verder...JavaScript heeft krachtige semantiek voor looping door arrays en array-achtige objecten. Ik'heb het antwoord in twee delen gesplitst: Opties voor echte arrays, en opties voor dingen die gewoon array-achtig zijn, zoals het arguments
object, andere iterable objecten (ES2015+), DOM collecties, enzovoort.
Ik'zal snel opmerken dat je de ES2015 opties nu kunt gebruiken, zelfs op ES5 engines, door transpiling van ES2015 naar ES5. Zoek naar "ES2015 transpiling" / "ES6 transpiling" voor meer...
Oké, laten we eens kijken naar onze opties:
Je hebt drie opties in ECMAScript 5 ("ES5"), de versie die op dit moment het breedst ondersteund wordt, en nog twee die toegevoegd zijn in ECMAScript 2015 ("ES2015", "ES6"):
forEach
en aanverwanten (ES5+)for
lusfor-in
correctfor-of
(gebruik impliciet een iterator) (ES2015+)forEach
en aanverwanteIn elke vaag-moderne omgeving (dus niet IE8) waar je toegang hebt tot de Array
functies die door ES5 zijn toegevoegd (direct of met behulp van polyfills), kun je forEach
(spec
| MDN
) gebruiken:
var a = ["a", "b", "c"];
a.forEach(function(entry) {
console.log(entry);
});
forEach
accepteert een callback functie en, optioneel, een waarde om te gebruiken als this
bij het aanroepen van die callback (hierboven niet gebruikt). De callback wordt aangeroepen voor elke entry in de array, in volgorde, waarbij niet-bestaande entries in sparse arrays worden overgeslagen. Hoewel ik hierboven maar één argument gebruikte, wordt de callback aangeroepen met drie: De waarde van elke entry, de index van die entry, en een verwijzing naar de array waar je're iteratie over gaat (voor het geval je functie die niet al bij de hand heeft).
Tenzij je'verouderde browsers zoals IE8 ondersteunt (die NetApps laat zien op iets meer dan 4% marktaandeel vanaf dit schrijven in september 2016), kun je gelukkig forEach
gebruiken in een algemene webpagina zonder een shim. Als je toch verouderde browsers moet ondersteunen, is shimmen/polyfillen van forEach
eenvoudig te doen (zoek naar "es5 shim" voor verschillende opties).
forEach
heeft het voordeel dat je geen index en waarde variabelen in de containing scope hoeft te declareren, omdat ze als argumenten aan de iteratie functie worden meegegeven, en dus mooi scoped naar alleen die iteratie.
Als je je zorgen maakt over de runtime kosten van het maken van een functie-aanroep voor elke array entry, wees dat dan niet; details.
Bovendien, forEach
is de "loop through them all" functie, maar ES5 definieerde verschillende andere nuttige "work your way through the array and do things" functies, waaronder:
every
(stopt de lus de eerste keer dat de callback false
of iets vals teruggeeft)some
(stopt met de eerste keer dat de callback true
of iets waarachtigs teruggeeft)filter
(creëert een nieuwe array met elementen waar de filter functie true
retourneert en met weglating van de elementen waar het false
retourneert)map
(creëert een nieuwe array van de waarden geretourneerd door de callback)reduce
(bouwt een waarde op door herhaaldelijk de callback aan te roepen, waarbij eerdere waarden worden doorgegeven; zie de spec voor de details; handig voor het optellen van de inhoud van een array en vele andere dingen)reduceRight
(zoals reduce
, maar werkt in aflopende in plaats van oplopende volgorde)for
lusSoms zijn de oude manieren de beste:
var index;
var a = ["a", "b", "c"];
for (index = 0; index < a.length; ++index) {
console.log(a[index]);
}
Als de lengte van de array niet zal veranderen tijdens de lus, en het is in performance-gevoelige code (onwaarschijnlijk), een iets gecompliceerdere versie die de lengte op voorhand grijpt zou een tiny beetje sneller kunnen zijn:
var index, len;
var a = ["a", "b", "c"];
for (index = 0, len = a.length; index < len; ++index) {
console.log(a[index]);
}
En/of terug tellen:
var index;
var a = ["a", "b", "c"];
for (index = a.length - 1; index >= 0; --index) {
console.log(a[index]);
}
Maar met moderne JavaScript engines is het zeldzaam dat je dat laatste beetje sap moet uitpersen.
In ES2015 en hoger, kun je je index en waarde variabelen lokaal maken voor de for
loop:
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"
}
En als je dat doet, wordt niet alleen value
maar ook index
opnieuw aangemaakt voor elke lus iteratie, wat betekent dat closures die in de lus body worden gemaakt een verwijzing houden naar de index
(en value
) die voor die specifieke iteratie is aangemaakt:
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>
Als je vijf divs had, zou je "Index is: 0" als je op de eerste klikte en "Index is: 4" als je op de laatste hebt geklikt. Dit werkt niet als u var
gebruikt in plaats van let
.
for-in
correct.Je krijgt van mensen te horen dat je for-in
moet gebruiken, maar dat is niet waar for-in
voor is. for-in
loopt door de numerable properties van een object, niet de indexen van een array. De volgorde is niet gegarandeerd, zelfs niet in ES2015 (ES6). ES2015+ definieert wel een volgorde voor objecteigenschappen (via [[OwnPropertyKeys]]
, [[Enumerate]]
, en dingen die ze gebruiken zoals Object.getOwnPropertyKeys
), maar het definieert niet dat for-in
die volgorde zal volgen. (Details in dit andere antwoord.)
De enige echte use-cases voor for-in
op een array zijn:
het is een [sparse arrays]14 met grote gaten erin, of
U gebruikt niet-elementeigenschappen en die wilt u in de lus opnemen
Als we alleen naar dat eerste voorbeeld kijken: Je kunt for-in
gebruiken om die sparese array elementen te bezoeken als je de juiste voorzorgsmaatregelen gebruikt:
// `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]);
}
}
Let op de drie controles:
lengte
kan hebben. (B.v., de lengte van een array's past in een 32-bit unsigned integer.) (Props aan RobG voor het erop wijzen in een commentaar op mijn blog post dat mijn vorige test'niet helemaal juist was.)
Je zou dat natuurlijk niet doen in inline code. Je'zou een utility functie schrijven. Misschien:
// 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
(gebruik impliciet een iterator) (ES2015+)ES2015 voegt iterators toe aan JavaScript. De makkelijkste manier om iterators te gebruiken is het nieuwe for-of
statement. Het ziet er als volgt uit:
const a = ["a", "b", "c"];
for (const val of a) {
console.log(val);
}
Onder de deksels, dat krijgt een iterator van de array en lus doorheen, het krijgen van de waarden van het. Dit heeft niet het probleem dat het gebruik van for-in
heeft, want het gebruikt een iterator gedefinieerd door het object (de array), en arrays definiëren dat hun iterators itereren door hun entries (niet hun eigenschappen). In tegenstelling tot for-in
in ES5, is de volgorde waarin de entries worden bezocht de numerieke volgorde van hun indexen.
Soms wil je een iterator expliciet gebruiken. Dat kan ook, hoewel het een stuk onhandiger is dan for-of
. Het ziet er als volgt uit:
const a = ["a", "b", "c"];
const it = a.values();
let entry;
while (!(entry = it.next()).done) {
console.log(entry.value);
}
De iterator is een object dat overeenkomt met de Iterator definitie in de specificatie. Zijn next
methode retourneert een nieuw resultaat object elke keer dat je het aanroept. Het resultaat object heeft een eigenschap, done
, die ons vertelt of het klaar is, en een eigenschap value
met de waarde voor die iteratie. (done
is optioneel als het false
zou zijn, value
is optioneel als het undefined
zou zijn).
De betekenis van value
varieert afhankelijk van de iterator; arrays ondersteunen (ten minste) drie functies die iterators teruggeven:
values()
: Dit is degene die ik hierboven heb gebruikt. Het geeft een iterator terug waarbij elke waarde
de array entry is voor die iteratie ("a"
, "b"
, en "c"
in het voorbeeld hierboven).keys()
: Geeft een iterator terug waarbij elke waarde
de sleutel is voor die iteratie (dus voor onze a
hierboven, zou dat "0"
zijn, dan "1"
, dan "2"
).entries()
: Geeft een iterator terug waarbij elke waarde
een array is in de vorm [sleutel, waarde]
voor die iteratie.Behalve echte arrays, zijn er ook array-achtige objecten die een lengte
eigenschap hebben en eigenschappen met numerieke namen: NodeList
instanties, het argumenten
object, enz. Hoe lussen we door hun inhoud?
Ten minste enkele, en mogelijk de meeste of zelfs alle, van de bovenstaande array-benaderingen zijn vaak even goed van toepassing op array-achtige objecten:
Gebruik forEach
en aanverwante (ES5+)
De verschillende functies op Array.prototype
zijn "intentionally generic" en kunnen meestal worden gebruikt op array-achtige objecten via Function#call
of Function#apply
. (Zie de Caveat voor host-provided objecten aan het eind van dit antwoord, maar het's een zeldzaam probleem).
Stel dat je forEach
wilde gebruiken op een Node
's childNodes
eigenschap. Dan zou je dit doen:
Array.prototype.forEach.call(node.childNodes, function(child) {
// Doe iets met child
});
Als je dat veel gaat doen, wil je misschien een kopie van de functieverwijzing in een variabele stoppen voor hergebruik, bijv:
// (Dit is allemaal vermoedelijk in een of andere scoping functie)
var forEach = Array.prototype.forEach;
// Dan later...
forEach.call(node.childNodes, function(child) {
// Doe iets met child
});
Gebruik een eenvoudige voor
lus
Vanzelfsprekend is een eenvoudige for
lus van toepassing op array-achtige objecten.
Gebruik for-in
correct
for-in
met dezelfde voorzorgsmaatregelen als bij een array zou ook moeten werken met array-achtige objecten; het voorbehoud voor host-voorziene objecten op #1 hierboven kan van toepassing zijn.
Gebruik for-of
(gebruik impliciet een iterator) (ES2015+)
for-of
zal de door het object verschafte iterator gebruiken (als die er is); we'zullen moeten zien hoe dit werkt met de verschillende array-achtige objecten, in het bijzonder met door de host verschafte objecten. Bijvoorbeeld, de specificatie voor de NodeList
van querySelectorAll
werd bijgewerkt om iteratie te ondersteunen. De specificatie voor de HTMLCollection
van getElementsByTagName
niet.
Gebruik expliciet een iterator (ES2015+) Zie #4, we'moeten nog even kijken hoe iterators uitpakken.
Andere keren wil je misschien een array-achtig object omzetten in een echte array. Dat doen is verrassend eenvoudig:
Gebruik de slice
methode van arrays
We kunnen de slice
methode van arrays gebruiken, die net als de andere hierboven genoemde methoden "intentionally generic" is en dus gebruikt kan worden met array-achtige objecten, zoals dit:
var trueArray = Array.prototype.slice.call(arrayLikeObject);
Dus als we bijvoorbeeld een NodeList
willen omzetten in een true array, dan zouden we dit kunnen doen:
var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
Zie de Caveat voor door de host geleverde objecten hieronder. Merk in het bijzonder op dat dit zal mislukken in IE8 en eerder, die niet toestaan dat je host-provided objecten gebruikt als this
op deze manier.
Gebruik spread syntax (...
)
Het'is ook mogelijk om ES2015's spread syntax te gebruiken met JavaScript engines die deze functie ondersteunen:
var trueArray = [...iterableObject];
Dus als we bijvoorbeeld een NodeList
willen omzetten in een true array, dan wordt dit met spread syntaxis heel beknopt:
var divs = [...document.querySelectorAll("div")];
Gebruik Array.from
(spec) | (MDN)
Array.from
(ES2015+, maar gemakkelijk polyfilled) maakt een array van een array-achtig object, waarbij optioneel de entries eerst door een mapping functie worden gehaald. Dus:
var divs = Array.from(document.querySelectorAll("div"));
Of als je een array van de tagnamen van de elementen met een bepaalde klasse wilt krijgen, zou je'de mapping functie gebruiken:
// Pijl functie (ES2015):
var divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);
// Standaard functie (aangezien Array.from
kan worden geshimmed):
var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
return element.tagName;
});
Als u Array.prototype
functies gebruikt met host-provided array-achtige objecten (DOM lijsten en andere dingen geleverd door de browser in plaats van de JavaScript engine), moet u er zeker van zijn dat u test in uw doel omgevingen om er zeker van te zijn dat het host-provided object zich goed gedraagt. De meeste gedragen zich goed (nu), maar het is belangrijk om te testen. De reden is dat de meeste Array.prototype
methoden die u waarschijnlijk zult willen gebruiken, afhankelijk zijn van het feit of het host-object een eerlijk antwoord geeft op de abstracte [[HasProperty]]
operatie. Op het moment dat ik dit schrijf, doen browsers dit erg goed, maar de 5.1 spec voorzag in de mogelijkheid dat een door de host verschaft object niet eerlijk zou kunnen zijn. Het staat in §8.6.2, enkele alinea's onder de grote tabel aan het begin van die sectie), waar het zegt:
Host-objecten mogen deze interne methoden op elke manier implementeren, tenzij anders gespecificeerd; een mogelijkheid is bijvoorbeeld dat
[[Get]]
en[[Put]]
voor een bepaald host-object inderdaad property-waarden ophalen en opslaan, maar[[HasProperty]]
genereert altijd false. (Ik kon de equivalente woorden niet vinden in de ES2015 spec, maar het zal nog steeds het geval zijn). Nogmaals, vanaf het moment dat ik dit schrijf, behandelen de gebruikelijke door de host geleverde array-achtige objecten in moderne browsers [NodeList
instanties, bijvoorbeeld] wel[[HasProperty]]
correct, maar het'is belangrijk om te testen).
Noot: Dit antwoord is hopeloos verouderd. Voor een modernere aanpak, kijk naar de methoden die beschikbaar zijn op een array. Methoden van belang kunnen zijn:
De standaard manier om een array te itereren in JavaScript is een vanilla for
-lus:
var length = arr.length,
element = null;
for (var i = 0; i < length; i++) {
element = arr[i];
// Do something with element
}
Merk echter op dat deze aanpak alleen goed is als je een dichte array hebt, en elke index wordt bezet door een element. Als de array sparse is, dan kun je performance problemen krijgen met deze aanpak, omdat je over veel indices zult itereren die echt niet bestaan in de array. In dit geval is een for .. in
-lus misschien een beter idee. Hoewel, je moet de juiste voorzorgsmaatregelen nemen om er zeker van te zijn dat alleen de gewenste eigenschappen van de array (dat wil zeggen, de array elementen) worden gebruikt, omdat de for..in
-lus ook opgesomd zal worden in oudere browsers, of als de extra eigenschappen gedefinieerd zijn als enumerable
.
In ECMAScript 5 komt er een forEach methode op het array prototype, maar deze wordt niet ondersteund in legacy browsers. Dus om het consistent te kunnen gebruiken moet je of een omgeving hebben die het ondersteunt (bijvoorbeeld Node.js voor server side JavaScript), of een "Polyfill" gebruiken. De Polyfill voor deze functionaliteit is echter triviaal en omdat het de code makkelijker leesbaar maakt, is het een goede polyfill om op te nemen.
Als je een lus wilt maken over een array, gebruik dan de standaard driedelige for
lus.
for (var i = 0; i < myArray.length; i++) {
var arrayItem = myArray[i];
}
Je kunt de prestaties optimaliseren door myArray.length
te cachen of er achterwaarts overheen te lopen.