Hur kan jag gå igenom alla poster i en array med JavaScript?
Jag trodde att det var något liknande:
forEach(instance in theArray)
Där theArray
är min array, men det verkar inte stämma.
TL;DR
for-in
om du inte använder det med skyddsåtgärder eller om du åtminstone är medveten om varför det kan bita dig.for-of
-slinga (endast ES2015+),Array#forEach
(spec
| MDN
) (eller dess släktingar some
och liknande) (endast ES5+),for
-slinga,for-in
med skyddsåtgärder.
Men det finns mycket mer att utforska, läs vidare...JavaScript har en kraftfull semantik för slinga genom arrayer och array-liknande objekt. Jag har delat upp svaret i två delar: Alternativ för äkta matriser och alternativ för saker som bara är array-liknande, som arguments
-objektet, andra iterable-objekt (ES2015+), DOM-samlingar och så vidare.
Jag ska snabbt notera att du kan använda ES2015-alternativen nu, även på ES5-motorer, genom att transportera ES2015 till ES5. Sök efter "ES2015 transpiling" / "ES6 transpiling" för mer...
Okej, låt oss titta på våra alternativ:
Du har tre alternativ i ECMAScript 5 ("ES5"), den version som har mest stöd för tillfället, och ytterligare två som lades till i ECMAScript 2015 ("ES2015", "ES6"):
forEach
och relaterade (ES5+)for
-slinga.for-in
korrekt.for-of
(använd implicit en iterator) (ES2015+)forEach
och relateradeI alla vagt moderna miljöer (alltså inte IE8) där du har tillgång till Array
-funktionerna som lades till i ES5 (direkt eller med hjälp av polyfills) kan du använda forEach
(spec
| MDN
):
var a = ["a", "b", "c"];
a.forEach(function(entry) {
console.log(entry);
});
forEach
accepterar en callback-funktion och, som alternativ, ett värde som ska användas som this
när callback-funktionen anropas (används inte ovan). Callback-funktionen anropas för varje post i matrisen, i turordning, och hoppar över icke-existerande poster i glesa matriser. Även om jag bara använde ett argument ovan, anropas callbacken med tre argument: Värdet för varje post, indexet för den posten och en referens till matrisen som du itererar över (om din funktion inte redan har den i handen).
Om du inte stöder föråldrade webbläsare som IE8 (som NetApps visar har en marknadsandel på drygt 4 % när detta skrivs i september 2016) kan du utan problem använda forEach
i en allmän webbsida utan shim. Om du behöver stödja föråldrade webbläsare är det lätt att shimma/polyfylla forEach
(sök efter "es5 shim" för flera alternativ).
forEach
har den fördelen att du inte behöver deklarera indexerings- och värdevariabler i det innehållande scope, eftersom de levereras som argument till iterationsfunktionen och därmed är väl avgränsade till just den iterationen.
Om du är orolig för körtidskostnaden för att göra ett funktionsanrop för varje arraypost behöver du inte vara det; details.
Dessutom är forEach
funktionen "loop through them all" men ES5 definierade flera andra användbara "work your way through the array and do things" funktioner, inklusive:
every
(slutar att slinga första gången som callbacken returnerar false
eller något annat felaktigt).some
(stoppar looping första gången callbacken returnerar true
eller något sanningsenligt)filter
(skapar en ny array med element där filterfunktionen returnerar true
och utelämnar de element där den returnerar false
).map
(skapar en ny array av de värden som återges av callback-funktionen).reduce
(bygger upp ett värde genom att upprepade gånger anropa callbacken och skicka in tidigare värden; se specifikationen för detaljer; användbart för att summera innehållet i en array och många andra saker)reduceRight
(som reduce
, men arbetar i fallande snarare än stigande ordning)for
-slingaIbland är de gamla sätten bäst:
var index;
var a = ["a", "b", "c"];
for (index = 0; index < a.length; ++index) {
console.log(a[index]);
}
Om längden på arrayen inte ändras under slingan och det är i prestandakänslig kod (osannolikt) kan en något mer komplicerad version som tar fram längden i förväg vara en tiny bit snabbare:
var index, len;
var a = ["a", "b", "c"];
for (index = 0, len = a.length; index < len; ++index) {
console.log(a[index]);
}
Och/eller att räkna baklänges:
var index;
var a = ["a", "b", "c"];
for (index = a.length - 1; index >= 0; --index) {
console.log(a[index]);
}
Men med moderna JavaScript-motorer är det sällan du behöver ta ut den sista biten juice.
I ES2015 och senare kan du göra dina index- och värdevariabler lokala för for
-slingan:
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"
}
Och när du gör det skapas inte bara value
utan även index
för varje loop-iteration, vilket innebär att closures som skapas i loop-kroppen behåller en referens till index
(och value
) som skapats för den specifika iterationen:
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>
Om du hade fem divs skulle du få "Index är: 0" om du klickar på den första och "Index är: 4" om du klickar på den sista. Detta fungerar inte om du använder var
istället för let
.
for-in
korrekt.Du'får folk som säger att du ska använda for-in
, men det'är inte vad for-in
är till för. for-in
loopar genom de uppräkneliga egenskaperna hos ett objekt, inte indexen i en array. Ordningsföljden är inte garanterad, inte ens i ES2015 (ES6). ES2015+ definierar en ordning för objektets egenskaper (via [[OwnPropertyKeys]]
, [[Enumerate]]
, och saker som använder dem som Object.getOwnPropertyKeys
), men det definierar inte att for-in
ska följa den ordningen. (Detaljer i detta andra svar.)
De enda verkliga användningsområdena för for-in
på en array är:
for-in
för att besöka dessa sparese array-element om du använder lämpliga skyddsåtgärder:
// `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]);
}
}
Observera de tre kontrollerna:
// 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
(använd implicit en iterator) (ES2015+)ES2015 lägger till iteratorer i JavaScript. Det enklaste sättet att använda iteratorer är det nya for-of
-kommandot. Det ser ut så här:
Det ser ut så här:
const a = ["a", "b", "c"];
for (const val of a) {
console.log(val);
}
Under täckmanteln hämtar detta en iterator från arrayen och loopar genom den och hämtar värdena från den. Detta har inte samma problem som att använda for-in
, eftersom det använder en iterator som definieras av objektet (matrisen), och matriser definierar att deras iteratorer itererar genom deras entries (inte deras egenskaper). Till skillnad från for-in
i ES5 är den ordning i vilken posterna besöks den numeriska ordningen för deras index.
Ibland kanske du vill använda en iterator explicit. Det kan du också göra, även om det är mycket krångligare än for-of
. Det ser ut så här:
const a = ["a", "b", "c"];
const it = a.values();
let entry;
while (!(entry = it.next()).done) {
console.log(entry.value);
}
Iteratorn är ett objekt som motsvarar Iteratordefinitionen i specifikationen. Dess next
-metod returnerar ett nytt resultatobjekt varje gång du anropar den. Resultatobjektet har en egenskap, done
, som talar om huruvida den är klar, och en egenskap value
med värdet för den iterationen. (done
är valfritt om det skulle vara false
, value
är valfritt om det skulle vara undefined
.)
Betydelsen av value
varierar beroende på iteratorn; matriser stöder (minst) tre funktioner som returnerar iteratorer:
values()
: Detta är den som jag använde ovan. Den returnerar en iterator där varje värde
är arrayposten för den iterationen ("a"
, "b"
och "c"
i exemplet tidigare).keys()
: Återger en iterator där varje "värde" är nyckeln för den iterationen (så för vår "a" ovan skulle det vara "0", sedan "1" och sedan "2").entries()
: Återger en iterator där varje värde
är en array i formen [nyckel, värde]
för den iterationen.Förutom äkta matriser finns det också matrisliknande objekt som har en egenskap length
och egenskaper med numeriska namn: NodeList
-instanser, arguments
-objektet osv. Hur loopar vi genom deras innehåll?
Åtminstone några, och möjligen de flesta eller till och med alla, av ovanstående metoder för matriser gäller ofta lika bra för matrisliknande objekt:
Använd forEach
och relaterade (ES5+)
De olika funktionerna i Array.prototype
är "avsiktligt generiska" och kan vanligtvis användas på array-liknande objekt via Function#call
eller Function#apply
. (Se Caveat för host-provided objects i slutet av det här svaret, men det är ett sällsynt problem.)
Anta att du vill använda forEach
på en Node
's childNodes
-egenskap. Du skulle göra så här:
Array.prototype.forEach.call(node.childNodes, function(child) {
// Gör något med child
.
});
Om du kommer att göra detta ofta kanske du vill ta en kopia av funktionsreferensen till en variabel för återanvändning, t.ex:
// (Allt detta är förmodligen i någon scoping-funktion).
var forEach = Array.prototype.forEach;
// Senare...
forEach.call(node.childNodes, function(child) {
// Gör något med child
.
});
Använd en enkel for
-slinga.
En enkel for
-slinga gäller naturligtvis för array-liknande objekt.
Använd for-in
korrekt.
for-in
med samma skyddsåtgärder som för en array bör fungera med array-liknande objekt också; förbehållet för host-provided objects på #1 ovan kan gälla.
Använd for-of
(använd implicit en iterator) (ES2015+)
for-of
kommer att använda den iterator som tillhandahålls av objektet (om det finns någon); vi måste se hur detta fungerar med de olika array-liknande objekten, särskilt de som tillhandahålls av värden. Specifikationen för NodeList
från querySelectorAll
har till exempel uppdaterats för att stödja iteration. Specifikationen för HTMLCollection
från getElementsByTagName
har inte ändrats.
Använd en iterator explicit (ES2015+) Se #4, vi'måste se hur iteratorer fungerar.
Andra gånger kanske du vill konvertera ett array-liknande objekt till en sann array. Att göra det är förvånansvärt enkelt:
Använd slice
metoden för arrays.
Vi kan använda slice
-metoden för arrays, som liksom de andra metoderna som nämns ovan är "avsiktligt generisk" och därför kan användas med array-liknande objekt, som här:
Det kan till exempel användas så här: var trueArray = Array.prototype.slice.call(arrayLikeObject);
Om vi till exempel vill konvertera en NodeList
till en true array kan vi göra så här:
Var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
Se Caveat för host-provided objects nedan. Observera särskilt att detta kommer att misslyckas i IE8 och tidigare, som inte låter dig använda host-provided objects som this
på detta sätt.
Använd spread syntax (...
)
Det är också möjligt att använda ES2015's spread syntax med JavaScript-motorer som stöder den här funktionen:
var trueArray = [...iterableObject];
Om vi till exempel vill konvertera en NodeList
till en true array blir det ganska kortfattat med spread syntax:
Var divs = [...document.querySelectorAll("div")];
Använd Array.from
(spec) | (MDN)
Array.from
(ES2015+, men lätt att fylla ut) skapar en array från ett arrayliknande objekt, och skickar eventuellt posterna genom en mappningsfunktion först. Så:
var divs = Array.from(document.querySelectorAll("div"));
Om du vill få fram en matris med taggnamnen för element med en viss klass använder du mappningsfunktionen:
// Arrow-funktion (ES2015):
Var divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);
// Standardfunktion (eftersom Array.from
kan vara shimmed):
var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
return element.tagName;
});
Om du använder Array.prototype
-funktioner med host-provided array-liknande objekt (DOM-listor och andra saker som tillhandahålls av webbläsaren snarare än JavaScript-motorn) måste du vara säker på att testa i dina målmiljöer för att se till att det host-provided objektet beter sig korrekt. De flesta beter sig korrekt (nu), men det är viktigt att testa. Anledningen är att de flesta metoderna i Array.prototype
som du sannolikt kommer att vilja använda är beroende av att det objekt som tillhandahålls av värden ger ett ärligt svar på den abstrakta operationen [[HasProperty]]
. I skrivande stund gör webbläsare ett mycket bra jobb med detta, men 5.1-specifikationen tillåter möjligheten att ett objekt som tillhandahålls av värden kanske inte är ärligt. Det står i §8.6.2, flera stycken under den stora tabellen i början av avsnittet, där det står:
En möjlighet är till exempel att [[Get]]
och [[Put]]
för ett visst värdobjekt verkligen hämtar och lagrar egenskapsvärden men att [[HasProperty]]
alltid genererar false.
(Jag kunde inte hitta motsvarande formulering i ES2015-specifikationen, men det är säkert fortfarande fallet.) Igen, i skrivande stund hanterar de vanliga array-liknande objekten i moderna webbläsare [t.ex. NodeList
-instanser] de facto [[HasProperty]]` korrekt, men det är viktigt att testa).
Note: Detta svar är hopplöst föråldrat. För ett mer modernt tillvägagångssätt, se de metoder som finns tillgängliga för en array. Metoder av intresse kan vara:
Standardmetoden för att iterera en array i JavaScript är en vanlig for
-slinga:
var length = arr.length,
element = null;
for (var i = 0; i < length; i++) {
element = arr[i];
// Do something with element
}
Observera dock att detta tillvägagångssätt endast är bra om du har en tät array och varje index är upptaget av ett element. Om arrayen är gles kan du få prestandaproblem med detta tillvägagångssätt, eftersom du kommer att iterera över en massa index som inte verkligen finns i arrayen. I det här fallet kan en for .. in
-slinga vara en bättre idé. **Du måste dock använda lämpliga skyddsåtgärder för att se till att endast de önskade egenskaperna hos matrisen (dvs. matrisens element) behandlas, eftersom for..in
-slingan också kommer att räknas upp i äldre webbläsare, eller om de ytterligare egenskaperna är definierade som enumerable
.
I ECMAScript 5 kommer det att finnas en forEach-metod på arrayprototypen, men den stöds inte i äldre webbläsare. Så för att kunna använda den konsekvent måste du antingen ha en miljö som stöder den (till exempel Node.js för JavaScript på serversidan) eller använda en "Polyfill". Polyfill för denna funktionalitet är dock trivial och eftersom den gör koden lättare att läsa är det en bra polyfill att inkludera.
Om du vill gå i en loop över en array använder du den vanliga tredelade for
-slingan.
for (var i = 0; i < myArray.length; i++) {
var arrayItem = myArray[i];
}
Du kan optimera prestandan genom att cachelagra myArray.length
eller genom att iterera över den baklänges.