Die Verwendung von forEach für ein Array aus getElementsByClassName führt zu "TypeError: undefined ist keine Funktion".

91

In meiner JSFiddle versuche ich einfach, über ein Array von Elementen zu iterieren. Das Array ist nicht leer, wie die Protokollanweisungen belegen. Der Aufruf von forEachgibt mir jedoch den (nicht so hilfreichen) Fehler "Nicht gefangen TypeError: undefinedist keine Funktion".

Ich muss etwas Dummes tun; Was mache ich falsch?

Mein Code:

var arr = document.getElementsByClassName('myClass');
console.log(arr);
console.log(arr[0]);
arr.forEach(function(v, i, a) {
  console.log(v);
});
.myClass {
  background-color: #FF0000;
}
<div class="myClass">Hello</div>

Jer
quelle
8
arrist kein Array, sondern ein HTMLCollection. Es hat nicht die gleichen Methoden wie ein Array. developer.mozilla.org/en-US/docs/Web/API/… . Hier ist sogar ein SO-Beitrag darüber: stackoverflow.com/questions/13433799/…
Ian
So etwas [1,2,3].forEach(function(v,i,a) { console.log(v); });ist in Ordnung. Was ist der Unterschied zwischen diesem und dem Array in meinem Beispiel?
Jer
Sie nicht ein haben Array in Ihrem Beispiel. Was lässt Sie denken, dass es ein Array ist?
Ian
3
@Jer: Da dies arr instanceof Arraydazu führen wird false, können keine Prototypmethoden des ArrayObjekts wie Array.prototype.forEach () verwendet werden . arrist eine HTMLCollection und ein Array-ähnliches Objekt (erbt jedoch nicht von oder instanziiert nicht Array). Daher forfunktioniert Ihre Standardschleife so, dass sie einfach den Index des Objekts durchläuft und kein Prototyp von ist Array.
Nein,
1
@ Jer - Sie sollten die Unterschiede zwischen integrierten und Host-Objekten untersuchen. Ersteres entspricht ECMA-262, letzteres nur so viel, wie der Gastgeber wünscht. Das DOM verfügt über viele Objekte, die den Zugriff auf Mitglieder über den Index ermöglichen (document.images, document.forms, form.elements, select.options usw.), hauptsächlich basierend auf der NodeList- Schnittstelle .
RobG

Antworten:

162

Dies liegt daran , dass document.getElementsByClassNameeine HTMLCollection zurückgegeben wird , kein Array.

Glücklicherweise handelt es sich um ein "Array-ähnliches" Objekt (was erklärt, warum es protokolliert wird, als wäre es ein Objekt, und warum Sie mit einer Standardschleife iterieren können for). Sie können dies also tun:

[].forEach.call(document.getElementsByClassName('myClass'), function(v,i,a) {

Mit ES6 (in modernen Browsern oder mit Babel) können Sie auch Array.fromArrays aus Array-ähnlichen Objekten erstellen:

Array.from(document.getElementsByClassName('myClass')).forEach(v=>{

oder verteilen Sie das Array-ähnliche Objekt in einem Array:

[...document.getElementsByClassName('myClass'))].forEach(v=>{
Denys Séguret
quelle
2
@ Jer argumentsist einer. jQuery-Objekte sind andere. Sie könnten selbst eine machen:var a = {"0": "str1", "1": "str2", length: 2}
Ian
1
Hier geht es wieder mit alten Browsern weiter. Das Übergeben eines Host-Objekts an eine native Methode schlägt in IE 8 und niedriger fehl. Vielleicht kümmert es niemanden, aber einige könnten. ;-) Oh, getElementsByClassName wird ebenfalls nicht unterstützt , querySelectorAll('.myClass')sollte aber funktionieren. Ich warte immer noch darauf, dass Iteratoren zur NodeList-API hinzugefügt werden. :-(
RobG
2
@Jer: Als Randnotiz, wenn Sie aus irgendeinem Grund aus der Schleife ausbrechen Array.prototype.forEachwollen, können Sie das nicht tun. Wenn Sie diese Anforderung später haben, verwenden Sie die Standardschleife foroder wenn Sie das Array-Objekt verwenden möchten, verwenden Sie Array.prototype.everyoder Array.prototype.some(beachten Sie jedoch, dass nicht alle in IE8 oder weniger unterstützt werden)
Nein,
1
@ Ian Sie benötigen Spleiß, damit das Objekt "Array-ähnlich" ist. Vergleichen Sie die Protokolle hier: jsbin.com/sigut/1/edit
Denys Séguret
1
@ Ian TBH Die Definition von "Array-like" ist sehr unscharf und hängt von der Verwendung ab. Manchmal enthalten ich nicht splicein dieser Definition , aber wenn ich mehr „Array-like“ sein will , nutzen zu können map, filterund so weiter, dann schließe ich es. Einfache Iteration mit forEachnicht erforderlich splice.
Denys Séguret
11

Versuchen Sie dies, es sollte funktionieren:

<html>
  <head>
    <style type="text/css">
    </style>
  </head>
  <body>
   <div class="myClass">Hello</div>
   <div class="myClass">Hello</div>

<script type="text/javascript">
    var arr = document.getElementsByClassName('myClass');
    console.log(arr);
    console.log(arr[0]);
    arr = [].slice.call(arr); //I have converted the HTML Collection an array
    arr.forEach(function(v,i,a) {
        console.log(v);
    });
</script>


<style type="text/css">
    .myClass {
    background-color: #FF0000;
}
</style>

  </body>
</html>
Vaibhav Jain
quelle
0

Für den Fall, dass Sie auf die ID jedes Elements einer bestimmten Klasse zugreifen möchten, können Sie Folgendes tun:

    Array.from(document.getElementsByClassName('myClass')).forEach(function(element) {
        console.log(element.id);
    });
Nelles
quelle