Użycie forEach na tablicy z getElementsByClassName skutkuje „TypeError: undefined nie jest funkcją”

91

W moim JSFiddle po prostu próbuję iterować po tablicy elementów. Tablica nie jest pusta, o czym świadczą instrukcje log. Jednak wezwanie do wywołania forEach(niezbyt pomocnego) błędu „Uncught TypeError: undefinedis not a function”.

Muszę robić coś głupiego; Co ja robię źle?

Mój kod:

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
źródło
8
arrnie jest tablicą, ale plikiem HTMLCollection. Nie ma tych samych metod, co tablica. developer.mozilla.org/en-US/docs/Web/API/… . Oto nawet post SO na ten temat: stackoverflow.com/questions/13433799/ ...
Ian
Coś jak [1,2,3].forEach(function(v,i,a) { console.log(v); });jest w porządku. Jaka jest różnica między tym a tablicą w moim przykładzie?
Jer
W swoim przykładzie nie masz tablicy . Co sprawia, że ​​myślisz, że to tablica?
Ian
3
@Jer: W arr instanceof Arrayrezultacie falsenie może korzystać z żadnych prototypowych metod Arrayobiektu, takich jak Array.prototype.forEach () . arrjest HTMLCollection i obiektem przypominającym tablicę (ale nie dziedziczy po nim ani nie tworzy instancji Array). W związku z tym standardowa forpętla będzie działać, ponieważ po prostu iteruje po indeksie obiektu i nie jest prototypem Array.
Nie,
1
@ Jer - należy przyjrzeć się różnicom między obiektami wbudowanymi i obiektami hosta. Pierwsza jest zgodna z ECMA-262, późniejsza tylko na tyle, na ile chce gospodarz. DOM ma wiele obiektów, które umożliwiają dostęp do członków według indeksu (document.images, document.forms, form.elements, select.options itp.), Głównie w oparciu o interfejs NodeList .
RobG

Odpowiedzi:

163

Dzieje się tak , ponieważ document.getElementsByClassNamezwraca HTMLCollection , a nie tablicę.

Na szczęście jest to obiekt przypominający tablicę (co wyjaśnia, dlaczego jest rejestrowany tak, jakby był obiektem i dlaczego można iterować za pomocą standardowej forpętli), więc możesz to zrobić:

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

W ES6 (w nowoczesnych przeglądarkach lub z Babel) możesz również użyć, Array.fromktóry buduje tablice z obiektów podobnych do tablic:

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

lub rozłóż obiekt podobny do tablicy na tablicę:

[...document.getElementsByClassName('myClass'))].forEach(v=>{
Denys Séguret
źródło
2
@Jer argumentsjest jeden. Obiekty jQuery to kolejne. Możesz zrobić to sam:var a = {"0": "str1", "1": "str2", length: 2}
Ian
1
Wracamy do starych przeglądarek… Przekazanie obiektu hosta do metody natywnej zakończy się niepowodzeniem w IE 8 i starszych. Może nikogo to nie obchodzi, ale niektórzy mogą. ;-) Och, to też nie obsługuje getElementsByClassName , ale querySelectorAll('.myClass')powinno działać. Nadal czekam na dodanie iteratorów do interfejsu API NodeList. :-(
RobG
2
@Jer: Na marginesie, jeśli zamierzasz wyrwać się z pętli z jakiegokolwiek powodu Array.prototype.forEach, nie pozwoli ci tego zrobić. Jeśli masz to wymaganie później, użyj standardowej forpętli lub jeśli chcesz użyć obiektu tablicy, użyj Array.prototype.everylub Array.prototype.some(zauważ jednak, że wszystkie / niektóre nie są obsługiwane w IE8 lub mniej)
Nie.
1
@Ian Potrzebujesz splice, aby obiekt był „podobny do tablicy”. Porównaj dzienniki tutaj: jsbin.com/sigut/1/edit
Denys Séguret
1
@Ian TBH definicja „podobnego do tablicy” jest bardzo rozmyta i zależy od zastosowania. Czasami nie uwzględniam splicew tej definicji, ale kiedy chcę być bardziej „podobny do tablicy”, aby móc używać map, filteritd., Dołączam ją. Prosta iteracja przy użyciu forEachnie wymaga splice.
Denys Séguret
11

Spróbuj tego, powinno działać:

<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
źródło
0

w przypadku, gdy chcesz uzyskać dostęp do identyfikatora każdego elementu określonej klasy, możesz wykonać następujące czynności:

    Array.from(document.getElementsByClassName('myClass')).forEach(function(element) {
        console.log(element.id);
    });
Nelles
źródło