For-each över en array i JavaScript?

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.

Lösning

TL;DR

  • Använd inte 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.
  • Dina bästa insatser är vanligtvis
  • en for-of-slinga (endast ES2015+),
  • Array#forEach ([spec][1] | [MDN][2]) (eller dess släktingar some och liknande) (endast ES5+),
  • en enkel gammaldags for-slinga,
  • eller 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:

    För faktiska matriser

    Du har tre alternativ i [ECMAScript 5][3] ("ES5"), den version som har mest stöd för tillfället, och ytterligare två som lades till i [ECMAScript 2015][4] ("ES2015", "ES6"):

  1. Använd forEach och relaterade (ES5+)
  2. Använd en enkel for-slinga.
  3. Använd for-in korrekt.
  4. Använd for-of (använd implicit en iterator) (ES2015+)
  5. Använd en iterator explicit (ES2015+) Detaljer:

    1. Använd forEach och relaterade

    I 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][1] | [MDN][2]):

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][5] (slutar att slinga första gången som callbacken returnerar false eller något annat felaktigt).
  • [some][6] (stoppar looping första gången callbacken returnerar true eller något sanningsenligt)
  • [filter][7] (skapar en ny array med element där filterfunktionen returnerar true och utelämnar de element där den returnerar false).
  • [map][8] (skapar en ny array av de värden som återges av callback-funktionen).
  • [reduce][9] (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][10] (som reduce, men arbetar i fallande snarare än stigande ordning)

    2. Använd en enkel for-slinga

    Ibland ä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.

3. Använd 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][11]. 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][12]), men det definierar inte att for-in ska följa den ordningen. (Detaljer i [detta andra svar][13].) De enda verkliga användningsområdena för for-in på en array är:

  • Det är en [sparse arrays][14] med massiva luckor i den, eller
  • Du använder egenskaper som inte är element och du vill inkludera dem i slingan. Om man bara tittar på det första exemplet: Du kan använda 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 
Kommentarer (24)

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:

  • forEach
  • Map
  • filter
  • zip
  • minska
  • varje
  • några

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.

Kommentarer (4)

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.

Kommentarer (5)