Wie kann ich mit JavaScript eine Schleife durch alle Einträge in einem Array ziehen?
Ich dachte, es wäre so etwas wie dies:
forEach(instance in theArray)
Wobei dasArray
mein Array ist, aber das scheint falsch zu sein.
TL;DR
Array#forEach
(spec
| MDN
) (oder seine Verwandten some
und so) (nur ES5+),for
-Schleife,for-in
mit Sicherheitsvorkehrungen.
Aber es gibt viel mehr zu entdecken, lesen Sie weiter...JavaScript hat eine mächtige Semantik für Schleifen durch Arrays und array-ähnliche Objekte. Ich habe die Antwort in zwei Teile aufgeteilt: 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 möchte kurz anmerken, dass Sie die ES2015-Optionen jetzt verwenden können, sogar auf ES5-Engines, indem Sie ES2015 nach ES5 transpilieren. Suchen Sie nach "ES2015 transpiling" / "ES6 transpiling" für mehr...
Okay, schauen wir uns unsere Optionen an:
Sie haben drei Optionen in ECMAScript 5 ("ES5"), der derzeit am meisten unterstützten Version, und zwei weitere in ECMAScript 2015 ("ES2015", "ES6"):
for-in
korrektfor-of
(verwenden Sie implizit einen Iterator) (ES2015+)forEach
und verwandteIn jeder halbwegs modernen Umgebung (also nicht IE8), in der Sie Zugriff auf die von ES5 hinzugefügten Array
-Funktionen haben (direkt oder über Polyfills), können Sie forEach
verwenden (spec
| MDN
):
var a = ["a", "b", "c"];
a.forEach(function(entry) {
console.log(entry);
});
forEach
akzeptiert eine Callback-Funktion und optional einen Wert, der als this
verwendet wird, wenn dieser Callback aufgerufen wird (oben nicht verwendet). Der Callback wird für jeden Eintrag im Array aufgerufen, und zwar der Reihe nach, wobei nicht existierende Einträge in spärlichen Arrays übersprungen werden. Obwohl ich oben nur ein Argument verwendet habe, wird der Callback mit drei Argumenten aufgerufen: Der Wert jedes Eintrags, der Index dieses Eintrags und ein Verweis auf das Array, über das Sie iterieren (falls Ihre Funktion es nicht bereits zur Hand hat).
Sofern Sie nicht veraltete Browser wie IE8 unterstützen (der laut NetApps zum Zeitpunkt der Erstellung dieses Artikels im September 2016 einen Marktanteil von etwas mehr als 4 % hat), können Sie "forEach" in einer allgemeinen Webseite problemlos ohne Shim verwenden. Wenn Sie veraltete Browser unterstützen müssen, ist es einfach, forEach
mit einem Shim/Polyfilling zu versehen (suchen Sie nach "es5 shim" für verschiedene Optionen).
forEach
hat den Vorteil, dass man keine Index- und Wertvariablen im enthaltenden Bereich deklarieren muss, da sie als Argumente an die Iterationsfunktion übergeben werden und so schön auf diese Iteration beschränkt sind.
Wenn Sie sich Sorgen über die Laufzeitkosten eines Funktionsaufrufs für jeden Array-Eintrag machen, brauchen Sie das nicht; details.
Zusätzlich ist forEach
die "Schleife durch alle" Funktion, aber ES5 definierte mehrere andere nützliche "arbeiten Sie sich durch das Array und tun Dinge" Funktionen, einschließlich:
every
(stoppt die Schleife, wenn der Callback das erste Mal false
oder etwas Falsches zurückgibt)some
(stoppt die Schleife, wenn der Callback das erste Mal true
oder etwas Wahres zurückgibt)filter
(erstellt ein neues Array, das die Elemente enthält, bei denen die Filterfunktion true
zurückgibt und diejenigen auslässt, bei denen sie false
zurückgibt)map
(erstellt ein neues Array aus den vom Callback zurückgegebenen Werten)reduce
(baut einen Wert auf, indem es den Callback wiederholt aufruft und die vorherigen Werte übergibt; siehe die Spezifikation für die Details; nützlich für die Summierung des Inhalts eines Arrays und viele andere Dinge)reduceRight
(wie reduce
, arbeitet aber in absteigender statt aufsteigender Reihenfolge)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 sich die Länge des Arrays während der Schleife nicht ändert und es sich um leistungssensiblen Code handelt (unwahrscheinlich), könnte eine etwas kompliziertere Version, die die Länge im Voraus abfragt, ein kleines bisschen schneller sein:
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ärtszählen:
var index;
var a = ["a", "b", "c"];
for (index = a.length - 1; index >= 0; --index) {
console.log(a[index]);
}
Aber mit modernen JavaScript-Engines ist es selten nötig, das letzte bisschen Saft herauszuholen. In ES2015 und höher können Sie Ihre Index- und Wertvariablen lokal in der "for"-Schleife platzieren:
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 das tun, wird nicht nur value
, sondern auch index
für jede Schleifeniteration neu erstellt, was bedeutet, dass die im Schleifenkörper erstellten Closures einen Verweis auf den für diese spezifische Iteration erstellten index
(und value
) behalten:
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 du fünf Divs hättest, würdest du "Index is: 0", wenn Sie auf das erste klicken und "Index is: 4", wenn Sie das letzte anklicken. Dies funktioniert nicht, wenn Sie var
anstelle von let
verwenden.
for-in
korrektMan wird Ihnen sagen, Sie sollen for-in
verwenden, aber dafür ist for-in
nicht gedacht. for-in
schleift durch die aufzählbaren Eigenschaften eines Objekts, nicht durch die Indizes eines Arrays. Die Reihenfolge ist nicht garantiert, nicht einmal in ES2015 (ES6). ES2015+ definiert zwar eine Reihenfolge für Objekteigenschaften (über [[OwnPropertyKeys]]
, [[Enumerate]]
und Dinge, die sie verwenden, wie Object.getOwnPropertyKeys
), aber es definiert nicht, dass for-in
dieser Reihenfolge folgt. (Details in dieser anderen Antwort.)
Die einzigen wirklichen Anwendungsfälle für for-in
auf einem Array sind:
for-in
verwenden, um diese spärlichen Array-Elemente zu besuchen, wenn Sie entsprechende Schutzmaßnahmen verwenden:
// `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:
// 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
(verwenden Sie einen Iterator implizit) (ES2015+)ES2015 führt Iteratoren in JavaScript ein. Der einfachste Weg, Iteratoren zu verwenden, ist die neue for-of
-Anweisung. Sie sieht wie folgt aus:
const a = ["a", "b", "c"];
for (const val of a) {
console.log(val);
}
Unter der Haube holt sich das einen Iterator aus dem Array und durchläuft ihn in einer Schleife, um die Werte daraus zu holen. Dies hat nicht das Problem, das for-in
hat, weil es einen Iterator verwendet, der durch das Objekt (das Array) definiert ist, und Arrays definieren, dass ihre Iteratoren durch ihre Einträge (nicht ihre Eigenschaften) iterieren. Im Gegensatz zu for-in
in ES5, ist die Reihenfolge, in der die Einträge besucht werden, die numerische Reihenfolge ihrer Indizes.
Manchmal möchten Sie vielleicht einen Iterator explizit verwenden. Das kann man auch tun, obwohl es viel umständlicher ist als for-of
. Es sieht wie folgt aus:
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 der Iterator-Definition in der Spezifikation entspricht. Seine Methode next
gibt bei jedem Aufruf ein neues Ergebnisobjekt zurück. Das Ergebnisobjekt hat eine Eigenschaft, done
, die uns sagt, ob es fertig ist, und eine Eigenschaft value
mit dem Wert für diese Iteration. (done
ist optional, wenn es false
wäre, value
ist optional, wenn es undefined
wäre.)
Die Bedeutung von value
variiert je nach Iterator; Arrays unterstützen (mindestens) drei Funktionen, die Iteratoren zurückgeben:
values()
: Dies ist die Funktion, die ich oben verwendet habe. Sie gibt einen Iterator zurück, bei dem jeder Wert
der Array-Eintrag für diese Iteration ist ("a"
, "b"
und "c"
in dem Beispiel zuvor).Schlüssel()
: Gibt einen Iterator zurück, bei dem jeder Wert
der Schlüssel für diese Iteration ist (für unser a
oben wäre das also "0"
, dann "1"
, dann "2"
).Einträge()
: Gibt einen Iterator zurück, bei dem jeder Wert
ein Array in der Form [Schlüssel, Wert]
für diese Iteration ist.Abgesehen von echten Arrays gibt es auch array-ähnliche Objekte, die eine Eigenschaft Länge
und Eigenschaften mit numerischen Namen haben: NodeList
-Instanzen, das Objekt arguments
usw. Wie können wir durch ihren Inhalt eine Schleife ziehen?
Zumindest einige und möglicherweise die meisten oder sogar alle der oben genannten Array-Ansätze gelten häufig auch für Array-ähnliche Objekte:
Nutzen Sie forEach
und verwandte (ES5+)
Die verschiedenen Funktionen von Array.prototype
sind "absichtlich generisch" und können normalerweise auf array-ähnliche Objekte über Function#call
oder Function#apply
verwendet werden. (Siehe den Caveat für host-provided objects am Ende dieser Antwort, aber das ist ein seltenes Problem.)
Nehmen wir an, Sie wollten forEach
auf die childNodes
Eigenschaft eines Node
anwenden. Sie würden dies tun:
Array.prototype.forEach.call(node.childNodes, function(child) {
// Etwas mit Kind
machen
});
Wenn Sie dies häufig tun, sollten Sie eine Kopie der Funktionsreferenz in eine Variable speichern, um sie wiederverwenden zu können, z. B:
// (Das alles ist vermutlich in einer Scoping-Funktion)
var forEach = Array.prototype.forEach;
// Dann später...
forEach.call(node.childNodes, function(child) {
// Etwas mit Kind
machen
});
Eine einfache "for"-Schleife verwenden Natürlich gilt eine einfache "for"-Schleife für array-ähnliche Objekte.
Verwenden Sie for-in
korrekt
for-in
mit den gleichen Sicherheitsvorkehrungen wie bei einem Array sollte auch mit Array-ähnlichen Objekten funktionieren; der Vorbehalt für vom Host zur Verfügung gestellte Objekte unter #1 oben kann gelten.
Verwenden Sie for-of
(verwenden Sie einen Iterator implizit) (ES2015+)
for-of
verwendet den Iterator, der vom Objekt bereitgestellt wird (falls vorhanden); wir müssen sehen, wie sich dies mit den verschiedenen array-ähnlichen Objekten verhält, insbesondere mit den vom Host bereitgestellten. Zum Beispiel wurde die Spezifikation für die NodeList
von querySelectorAll
aktualisiert, um Iteration zu unterstützen. Die Spezifikation für die HTMLCollection
von getElementsByTagName
wurde nicht aktualisiert.
Explizit einen Iterator verwenden (ES2015+) Siehe #4, wir müssen abwarten, wie sich Iteratoren auswirken.
In anderen Fällen möchten Sie vielleicht ein Array-ähnliches Objekt in ein echtes Array umwandeln. Das ist erstaunlich einfach:
Verwenden Sie die Slice
Methode von 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 verwendet werden kann, wie folgt:
var trueArray = Array.prototype.slice.call(arrayLikeObject);
Wenn wir also zum Beispiel eine NodeList
in ein echtes Array umwandeln wollen, könnten wir dies tun:
var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
Siehe den Caveat für vom Host bereitgestellte Objekte unten. Beachten Sie insbesondere, dass dies in IE8 und früheren Versionen fehlschlägt, da diese die Verwendung von host-provided Objekten als this
nicht zulassen.
Verwenden Sie spread syntax (...
)
Es ist auch möglich, die Spread-Syntax von ES2015 mit JavaScript-Engines zu verwenden, die diese Funktion unterstützen:
var trueArray = [...iterableObject];
Wenn wir also zum Beispiel eine NodeList
in ein echtes Array umwandeln wollen, wird dies mit der Spread-Syntax recht einfach:
var divs = [...document.querySelectorAll("div")];
Verwendung von Array.from
(spec) | (MDN)
Array.from
(ES2015+, aber leicht polyfilled) erstellt ein Array aus einem Array-ähnlichen Objekt, wobei die Einträge optional zuerst durch eine Mapping-Funktion geleitet werden. So:
var divs = Array.from(document.querySelectorAll("div"));
Oder wenn Sie ein Array der Tag-Namen der Elemente mit einer bestimmten Klasse erhalten möchten, würden Sie die Mapping-Funktion verwenden:
// Arrow-Funktion (ES2015):
var divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);
// Standardfunktion (da Array.from
abgeschwächt werden kann):
var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
return element.tagName;
});
Wenn Sie Array.prototype
-Funktionen mit host-provided array-ähnlichen Objekten (DOM-Listen und andere Dinge, die vom Browser und nicht von der JavaScript-Engine bereitgestellt werden) verwenden, müssen Sie sicherstellen, dass Sie in Ihren Zielumgebungen testen, ob sich das host-provided Objekt korrekt verhält. Die meisten verhalten sich richtig (jetzt), aber es ist wichtig, zu testen. Der Grund dafür ist, dass die meisten Array.prototype
-Methoden, die Sie wahrscheinlich verwenden wollen, sich darauf verlassen, dass das vom Host bereitgestellte Objekt eine ehrliche Antwort auf die abstrakte [[HasProperty]]
Operation gibt. Zum jetzigen Zeitpunkt machen die Browser das sehr gut, aber die 5.1 Spezifikation lässt die Möglichkeit zu, dass ein vom Host zur Verfügung gestelltes Objekt nicht ehrlich ist. Es steht in [§8.6.2] (http://www.ecma-international.org/ecma-262/5.1/#sec-8.6.2), einige Absätze unterhalb der großen Tabelle am Anfang des Abschnitts, wo es heißt:
Host-Objekte können diese internen Methoden auf beliebige Weise implementieren, sofern nicht anders angegeben; eine Möglichkeit ist zum Beispiel, dass
[[Get]]
und[[Put]]
für ein bestimmtes Host-Objekt zwar Eigenschaftswerte abrufen und speichern, aber[[HasProperty]]
immer false erzeugt. (Ich konnte die entsprechende Formulierung in der ES2015-Spezifikation nicht finden, aber es ist sicher immer noch der Fall). Auch hier gilt, dass die üblichen vom Host bereitgestellten Array-ähnlichen Objekte in modernen Browsern [z.B.NodeList
-Instanzen] korrekt mit[[HasProperty]]
umgehen, aber es ist wichtig zu testen).
Anmerkung: Diese Antwort ist hoffnungslos veraltet. Für einen moderneren Ansatz schauen Sie sich die für ein Array verfügbaren Methoden an. Methoden von Interesse könnten sein:
Die Standardmethode zur Iteration eines Arrays in JavaScript ist eine einfache "for"-Schleife:
var length = arr.length,
element = null;
for (var i = 0; i < length; i++) {
element = arr[i];
// Do something with element
}
Beachten Sie jedoch, dass dieser Ansatz nur gut ist, wenn Sie ein dichtes Array haben und jeder Index durch ein Element belegt ist. Wenn das Array dünn besiedelt ist, können Sie mit diesem Ansatz auf Leistungsprobleme stoßen, da Sie über viele Indizes iterieren, die nicht wirklich im Array existieren. In diesem Fall könnte eine "for .. in"-Schleife eine bessere Idee sein. **Sie müssen jedoch geeignete Sicherheitsvorkehrungen treffen, um sicherzustellen, dass nur die gewünschten Eigenschaften des Arrays (d.h. die Arrayelemente) bearbeitet werden, da die "for ...in"-Schleife in älteren Browsern ebenfalls aufgezählt wird, oder wenn die zusätzlichen Eigenschaften als "aufzählbar" definiert sind.
In ECMAScript 5 wird es eine forEach-Methode für den Array-Prototyp geben, aber sie wird in Legacy-Browsern nicht unterstützt. Um sie konsistent nutzen zu können, müssen Sie also entweder eine Umgebung haben, die sie unterstützt (z. B. Node.js für serverseitiges JavaScript), oder ein "Polyfill" verwenden. Das Polyfill für diese Funktionalität ist jedoch trivial, und da es den Code leichter lesbar macht, ist es ein gutes Polyfill, das man einbauen kann.
Wenn Sie eine Schleife über ein Array ziehen wollen, verwenden Sie die dreiteilige Standardschleife "for".
for (var i = 0; i < myArray.length; i++) {
var arrayItem = myArray[i];
}
Sie können einige Leistungsoptimierungen erreichen, indem Sie myArray.length
zwischenspeichern oder rückwärts darüber iterieren.