TL; DR
Aber es gibt noch viel mehr zu entdecken, weiterzulesen ...
JavaScript verfügt über eine leistungsstarke Semantik zum Durchlaufen von Arrays und Array-ähnlichen Objekten. Ich habe die Antwort in zwei Teile geteilt: Optionen für echte Arrays und Optionen für Dinge, die nur Array- ähnlich sind , wie das arguments
Objekt, andere iterierbare Objekte (ES2015 +), DOM-Sammlungen und so weiter.
Ich werde schnell beachten Sie, dass Sie die ES2015 - Optionen können nun auch auf ES5 - Motoren, von transpiling ES2015 bis ES5. Suchen Sie nach "ES2015 transpiling" / "ES6 transpiling" für mehr ...
Okay, schauen wir uns unsere Optionen an:
Für tatsächliche Arrays
Sie haben drei Optionen in ECMAScript 5 ("ES5"), der derzeit am weitesten verbreiteten Version, und zwei weitere in ECMAScript 2015 ("ES2015", "ES6"):
- Verwendung
forEach
und verwandte (ES5 +)
- Verwenden Sie eine einfache
for
Schleife
- verwenden Sie
for-in
richtig
- Verwenden
for-of
(implizit einen Iterator verwenden) (ES2015 +)
- Verwenden Sie explizit einen Iterator (ES2015 +)
Einzelheiten:
1. Verwenden forEach
und verwandt
In jeder vage modernen Umgebung (also nicht in IE8), in der Sie Zugriff auf die Array
von ES5 hinzugefügten Funktionen haben (direkt oder mithilfe von Polyfills), können Sie forEach
( spec
| MDN
) verwenden:
var a = ["a", "b", "c"];
a.forEach(function(entry) {
console.log(entry);
});
forEach
Akzeptiert eine Rückruffunktion und optional einen Wert, der this
beim Aufrufen dieses Rückrufs verwendet werden soll (oben nicht verwendet). Der Rückruf wird für jeden Eintrag im Array aufgerufen, um nicht vorhandene Einträge in dünn besetzten Arrays zu überspringen. Obwohl ich oben nur ein Argument verwendet habe, wird der Rückruf mit drei aufgerufen: Der Wert jedes Eintrags, der Index dieses Eintrags und ein Verweis auf das Array, über das Sie iterieren (falls Ihre Funktion es noch nicht zur Hand hat ).
Sofern Sie nicht veraltete Browser wie IE8 unterstützen (NetApps weist zum Zeitpunkt dieses Schreibens im September 2016 einen Marktanteil von etwas mehr als 4% auf), können Sie diese forEach
problemlos auf einer Allzweck-Webseite ohne Shim verwenden. Wenn Sie veraltete Browser unterstützen müssen, ist das Shimmen / Polyfilling forEach
einfach (suchen Sie nach "es5 shim" für mehrere Optionen).
forEach
hat den Vorteil, dass Sie keine Indexierungs- und Wertvariablen im enthaltenen Bereich deklarieren müssen, da sie als Argumente für die Iterationsfunktion bereitgestellt werden und sich so gut auf diese Iteration beschränken.
Wenn Sie sich Sorgen über die Laufzeitkosten für einen Funktionsaufruf für jeden Array-Eintrag machen, sollten Sie dies nicht tun. Details .
Darüber hinaus gibt forEach
es die Funktion "Schleife durch alle", aber ES5 hat mehrere andere nützliche Funktionen definiert, mit denen Sie sich durch das Array arbeiten und Dinge erledigen können, darunter:
every
(stoppt die Schleife, wenn der Rückruf zum ersten Mal zurückkehrt false
oder etwas Falsches)
some
(stoppt die Schleife, wenn der Rückruf zum ersten Mal zurückkehrt true
oder etwas Wahres)
filter
(Erstellt ein neues Array mit Elementen, bei denen die Filterfunktion zurückgegeben wird, true
und lässt diejenigen weg, bei denen sie zurückgegeben wird. false
)
map
(Erstellt ein neues Array aus den vom Rückruf zurückgegebenen Werten.)
reduce
(Baut einen Wert auf, indem der Rückruf wiederholt aufgerufen wird und vorherige Werte übergeben werden. Einzelheiten finden Sie in der Spezifikation. Nützlich zum Summieren des Inhalts eines Arrays und vieler anderer Dinge.)
reduceRight
(wie reduce
, funktioniert aber eher in absteigender als in aufsteigender Reihenfolge)
2. Verwenden Sie eine einfache for
Schleife
Manchmal sind die alten Wege die besten:
var index;
var a = ["a", "b", "c"];
for (index = 0; index < a.length; ++index) {
console.log(a[index]);
}
Wenn die Länge des Feldes wird nicht während der Schleife ändern, und es ist in leistungskritischen Code (unwahrscheinlich), eine etwas kompliziertere Version die Länge vorne greifen könnte ein seine winzig bisschen schneller:
var index, len;
var a = ["a", "b", "c"];
for (index = 0, len = a.length; index < len; ++index) {
console.log(a[index]);
}
Und / oder rückwärts zählen:
var index;
var a = ["a", "b", "c"];
for (index = a.length - 1; index >= 0; --index) {
console.log(a[index]);
}
Bei modernen JavaScript-Engines ist es jedoch selten, dass Sie das letzte Stück Saft herausholen müssen.
In ES2015 und höher können Sie Ihre Index- und Wertvariablen lokal für die for
Schleife festlegen :
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"
}
Und wenn Sie dies tun, wird nicht nur, value
sondern auch index
für jede Schleifeniteration neu erstellt, was bedeutet, dass im Schleifenkörper erstellte Abschlüsse einen Verweis auf das index
(und value
) enthalten, das für diese bestimmte Iteration erstellt wurde:
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>
Wenn Sie fünf Divs hatten, erhalten Sie "Index ist: 0", wenn Sie auf den ersten geklickt haben, und "Index ist: 4", wenn Sie auf den letzten geklickt haben. Dies funktioniert nicht , wenn Sie var
anstelle von verwenden let
.
3. for-in
Richtig verwenden
Sie erhalten die Menschen sagen , die Sie zu verwenden for-in
, aber das ist nicht das, was for-in
für ist . for-in
Durchläuft die aufzählbaren Eigenschaften eines Objekts und nicht die Indizes eines Arrays. Die Bestellung ist nicht garantiert , auch nicht in ES2015 (ES6). ES2015 + definiert zwar eine Reihenfolge für Objekteigenschaften (via [[OwnPropertyKeys]]
, [[Enumerate]]
und Dinge, die sie verwenden Object.getOwnPropertyKeys
), aber es wurde nicht definiert, for-in
welche Reihenfolge dieser Reihenfolge folgen würde. ES2020 hat es jedoch getan. (Details in dieser anderen Antwort .)
Die einzigen realen Anwendungsfälle für for-in
ein Array sind:
- Es ist eine spärliche Anordnung mit massiven Lücken, oder
- Sie verwenden Nicht-Elementeigenschaften und möchten diese in die Schleife aufnehmen
Betrachten Sie nur das erste Beispiel: Sie können for-in
diese spärlichen Array-Elemente verwenden, wenn Sie geeignete Sicherheitsvorkehrungen treffen:
// `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]);
}
}
Beachten Sie die drei Prüfungen:
Dass das Objekt eine eigene Eigenschaft mit diesem Namen hat (keine, die es von seinem Prototyp erbt), und
Dass der Schlüssel alle Dezimalstellen sind (z. B. normale Zeichenfolgenform, keine wissenschaftliche Notation), und
Dass der Wert des Schlüssels, wenn er zu einer Zahl gezwungen wird, <= 2 ^ 32 - 2 ist (was 4,294,967,294 ist). Woher kommt diese Nummer? Es ist Teil der Definition eines Array-Index in der Spezifikation . Andere Zahlen (Nicht-Ganzzahlen, negative Zahlen, Zahlen größer als 2 ^ 32 - 2) sind keine Array-Indizes. Der Grund, warum es 2 ^ 32 - 2 ist, ist, dass dadurch der größte Indexwert niedriger als 2 ^ 32 - 1 wird , was der maximale Wert ist, den ein Array haben length
kann. (ZB passt die Länge eines Arrays in eine 32-Bit-Ganzzahl ohne Vorzeichen.) (Wir bitten RobG, in einem Kommentar in meinem Blog-Beitrag darauf hinzuweisen, dass mein vorheriger Test nicht ganz richtig war.)
Das würden Sie natürlich nicht im Inline-Code tun. Sie würden eine Dienstprogrammfunktion schreiben. Vielleicht:
// 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);
});
4. Verwenden Sie for-of
(verwenden Sie implizit einen Iterator) (ES2015 +)
ES2015 fügte Iteratoren zu JavaScript hinzu. Der einfachste Weg, Iteratoren zu verwenden, ist die neue for-of
Anweisung. Es sieht aus wie das:
const a = ["a", "b", "c"];
for (const val of a) {
console.log(val);
}
Unter der Decke erhält das einen Iterator aus dem Array und durchläuft es, wobei die Werte daraus abgerufen werden. Dies hat nicht das Problem, das bei der Verwendung for-in
auftritt, da ein vom Objekt (dem Array) definierter Iterator verwendet wird und Arrays definieren, dass ihre Iteratoren ihre Einträge (nicht ihre Eigenschaften) durchlaufen . Anders als for-in
in ES5 ist die Reihenfolge, in der die Einträge besucht werden, die numerische Reihenfolge ihrer Indizes.
5. Verwenden Sie explizit einen Iterator (ES2015 +)
Manchmal möchten Sie möglicherweise einen Iterator explizit verwenden . Sie können das auch tun, obwohl es viel klobiger ist als for-of
. Es sieht aus wie das:
const a = ["a", "b", "c"];
const it = a.values();
let entry;
while (!(entry = it.next()).done) {
console.log(entry.value);
}
Der Iterator ist ein Objekt, das mit der Iteratordefinition in der Spezifikation übereinstimmt. Die next
Methode gibt bei jedem Aufruf ein neues Ergebnisobjekt zurück . Das Ergebnisobjekt hat eine Eigenschaft, done
die uns mitteilt, ob es fertig ist, und eine Eigenschaft value
mit dem Wert für diese Iteration. ( done
ist optional, wenn es wäre false
, value
ist optional, wenn es wäre undefined
.)
Die Bedeutung von value
variiert je nach Iterator. Arrays unterstützen (mindestens) drei Funktionen, die Iteratoren zurückgeben:
values()
: Dies ist die, die ich oben verwendet habe. Es gibt einen Iterator , wobei jedes value
der Array - Eintrag für diese Iteration ( "a"
, "b"
, und "c"
in dem Beispiel weiter oben).
keys()
: Gibt einen Iterator zurück, wobei jeder value
der Schlüssel für diese Iteration ist (für unsere a
obigen wäre das also "0"
dann "1"
, dann "2"
).
entries()
: Gibt einen Iterator zurück, wobei jedes value
ein Array im Formular [key, value]
für diese Iteration ist.
Für Array-ähnliche Objekte
Abgesehen von echten Arrays gibt es auch Array-ähnliche Objekte, die eine length
Eigenschaft und Eigenschaften mit numerischen Namen haben: NodeList
Instanzen, das arguments
Objekt usw. Wie durchlaufen wir ihren Inhalt?
Verwenden Sie eine der oben genannten Optionen für Arrays
Zumindest einige und möglicherweise die meisten oder sogar alle der oben genannten Array-Ansätze gelten häufig gleich gut für Array-ähnliche Objekte:
Verwendung forEach
und verwandte (ES5 +)
Die verschiedenen Funktionen Array.prototype
sind "absichtlich generisch" und können normalerweise für Array-ähnliche Objekte über Function#call
oder verwendet werden Function#apply
. (Siehe die Einschränkung für vom Host bereitgestellte Objekte am Ende dieser Antwort, aber es ist ein seltenes Problem.)
Angenommen , Sie verwenden wollten forEach
auf einem Node
‚s - childNodes
Eigenschaft. Du würdest das tun:
Array.prototype.forEach.call(node.childNodes, function(child) {
// Do something with `child`
});
Wenn Sie dies häufig tun, möchten Sie möglicherweise eine Kopie der Funktionsreferenz zur Wiederverwendung in eine Variable aufnehmen, z.
// (This is all presumably in some scoping function)
var forEach = Array.prototype.forEach;
// Then later...
forEach.call(node.childNodes, function(child) {
// Do something with `child`
});
Verwenden Sie eine einfache for
Schleife
Offensichtlich gilt eine einfache for
Schleife für Array-ähnliche Objekte.
verwenden Sie for-in
richtig
for-in
mit den gleichen Sicherheitsvorkehrungen wie mit einem Array sollte auch mit Array-ähnlichen Objekten funktionieren; Möglicherweise gilt die Einschränkung für vom Host bereitgestellte Objekte unter Nummer 1 oben.
Verwenden for-of
(implizit einen Iterator verwenden) (ES2015 +)
for-of
verwendet den vom Objekt bereitgestellten Iterator (falls vorhanden). Dies schließt vom Host bereitgestellte Objekte ein. Beispielsweise wurde die Spezifikation für das NodeList
von querySelectorAll
aktualisiert, um die Iteration zu unterstützen. Die Spezifikation für das HTMLCollection
von getElementsByTagName
war nicht.
Verwenden Sie explizit einen Iterator (ES2015 +)
Siehe # 4.
Erstellen Sie ein echtes Array
In anderen Fällen möchten Sie möglicherweise ein Array-ähnliches Objekt in ein echtes Array konvertieren. Das ist überraschend einfach:
Verwenden Sie die slice
Methode der Arrays
Wir können die slice
Methode von Arrays verwenden, die wie die anderen oben genannten Methoden "absichtlich generisch" ist und daher mit Array-ähnlichen Objekten wie diesen verwendet werden kann:
var trueArray = Array.prototype.slice.call(arrayLikeObject);
Wenn wir beispielsweise a NodeList
in ein echtes Array konvertieren möchten , können wir Folgendes tun:
var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
Weitere Informationen zu vom Host bereitgestellten Objekten finden Sie weiter unten. Beachten Sie insbesondere, dass dies in IE8 und früheren Versionen fehlschlägt, sodass Sie vom Host bereitgestellte Objekte nicht so verwenden können this
.
Verwenden Sie die Spread-Syntax ( ...
)
Es ist auch möglich, die Spread-Syntax von ES2015 mit JavaScript-Engines zu verwenden, die diese Funktion unterstützen. Wie for-of
dies den verwendet Iterator vom Objekt zur Verfügung gestellt (siehe # 4 im vorherigen Abschnitt):
var trueArray = [...iterableObject];
Wenn wir zum Beispiel a NodeList
in ein echtes Array konvertieren möchten , wird dies mit der Spread-Syntax ziemlich prägnant:
var divs = [...document.querySelectorAll("div")];
Verwenden Array.from
Array.from
(spec) | (MDN) (ES2015 +, aber leicht polyfüllbar) erstellt ein Array aus einem Array-ähnlichen Objekt, wobei die Einträge optional zuerst über eine Zuordnungsfunktion übergeben werden. Damit:
var divs = Array.from(document.querySelectorAll("div"));
Wenn Sie ein Array der Tag-Namen der Elemente mit einer bestimmten Klasse abrufen möchten, verwenden Sie die Zuordnungsfunktion:
// Arrow function (ES2015):
var divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);
// Standard function (since `Array.from` can be shimmed):
var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
return element.tagName;
});
Vorsichtsmaßnahme für vom Host bereitgestellte Objekte
Wenn Sie Array.prototype
Funktionen mit vom Host bereitgestellten Array-ähnlichen Objekten verwenden (DOM-Listen und andere vom Browser anstelle der JavaScript-Engine bereitgestellte Dinge), müssen Sie sicherstellen, dass Sie in Ihren Zielumgebungen testen, um sicherzustellen, dass sich das vom Host bereitgestellte Objekt ordnungsgemäß verhält . Die meisten verhalten sich (jetzt) richtig , aber es ist wichtig zu testen. Der Grund dafür ist, dass die meisten Array.prototype
Methoden, die Sie wahrscheinlich verwenden möchten, auf dem vom Host bereitgestellten Objekt basieren, das eine ehrliche Antwort auf die abstrakte [[HasProperty]]
Operation gibt. Zum jetzigen Zeitpunkt leisten Browser sehr gute Arbeit, aber die 5.1-Spezifikation hat die Möglichkeit berücksichtigt, dass ein vom Host bereitgestelltes Objekt möglicherweise nicht ehrlich ist. Es ist in §8.6.2 , mehrere Absätze unter der großen Tabelle am Anfang dieses Abschnitts), wo es heißt:
Hostobjekte können diese internen Methoden auf jede Art und Weise implementieren, sofern nicht anders angegeben. zum Beispiel ist eine Möglichkeit , dass , [[Get]]
und [[Put]]
für ein bestimmtes Host - Objekt in der Tat Abruf- und Speichereigenschaftswerte aber [[HasProperty]]
immer erzeugt falsch .
(Ich konnte nicht den äquivalent Wortschwall in der ES2015 - Spezifikation finden, aber es ist gesprungen , der Fall noch sein.) Auch hier, wie dies den gemeinsamen Host-bereitgestellt Schreiben Array-ähnliche Objekte in modernen Browsern [ NodeList
Instanzen, zum Beispiel] tun Griffes [[HasProperty]]
richtig, aber es ist wichtig zu testen.)
forEach
und nicht nurfor
. wie gesagt, in c # war es ein bisschen anders, und das hat mich verwirrt :)