Использование forEach в массиве из getElementsByClassName приводит к ошибке «TypeError: undefined не является функцией»

92

В моем JSFiddle я просто пытаюсь перебрать массив элементов. Как показывают операторы журнала, массив не пуст. Однако вызов to forEachдает мне (не очень полезную) ошибку «Uncaught TypeError: undefinedis not a function».

Я, должно быть, делаю что-то глупое; Что я делаю не так?

Мой код:

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>

Джер
источник
8
arrне массив, а HTMLCollection. У него нет тех же методов, что и у массива. developer.mozilla.org/en-US/docs/Web/API/… . Вот даже SO-сообщение об этом: stackoverflow.com/questions/13433799/…
Ян
Что-то вроде [1,2,3].forEach(function(v,i,a) { console.log(v); });нормально. В чем разница между этим и массивом в моем примере?
Джер
Вы не имеете массив в вашем примере. Что заставляет вас думать, что это массив?
Ян
3
@Jer: В arr instanceof Arrayрезультате falseон не может использовать какие-либо методы прототипа Arrayобъекта, такие как Array.prototype.forEach () . arrпредставляет собой HTMLCollection и массив, подобный объекту (но не наследуется и не создается Array). Следовательно, ваш стандартный forцикл будет работать так, как будто он просто перебирает индекс объекта и не является прототипом Array.
Нет,
1
@ Jer - вам следует изучить различия между встроенными и ведущими объектами. Первые соответствуют ECMA-262, а вторые - настолько, насколько желает хост. DOM имеет множество объектов, которые разрешают доступ к членам по индексу (document.images, document.forms, form.elements, select.options и т. Д.), В основном на основе интерфейса NodeList .
RobG

Ответы:

164

Это потому, что document.getElementsByClassNameвозвращает HTMLCollection , а не массив.

К счастью, это объект типа «массив» (что объясняет, почему он регистрируется как объект и почему вы можете выполнять итерацию с помощью стандартного forцикла), поэтому вы можете сделать это:

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

С ES6 (в современных браузерах или с Babel) вы также можете использовать, Array.fromкоторый строит массивы из объектов, подобных массиву:

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

или разложите подобный массиву объект в массив:

[...document.getElementsByClassName('myClass'))].forEach(v=>{
Дени Сегюре
источник
2
@Jer arguments- один. Другой пример - объекты jQuery. Вы могли бы сделать его сами:var a = {"0": "str1", "1": "str2", length: 2}
Ян
1
Здесь мы снова идем со старыми браузерами ... передача хост-объекта в собственный метод не удастся в IE 8 и ниже. Может, никого это не волнует, но некоторым может быть. ;-) О, он тоже не поддерживает getElementsByClassName , но querySelectorAll('.myClass')должен работать. Я все еще жду добавления итераторов в API NodeList. :-(
RobG
2
@Jer: В качестве примечания, если вы намереваетесь выйти из цикла по какой-либо причине Array.prototype.forEach, вам этого не позволят. Если у вас есть это требование позже, используйте стандартный forцикл или если вы хотите использовать объект массива, используйте Array.prototype.everyили Array.prototype.some(обратите внимание, что каждый / некоторые не поддерживаются в IE8 или менее)
Нет,
1
@Ian Вам нужно сращивание, чтобы объект стал "подобным массиву". Сравните логи здесь: jsbin.com/sigut/1/edit
Denys Séguret
1
@Ian TBH определение «подобного массиву» очень расплывчато и зависит от использования. Иногда я не включает spliceв это определение , но когда я хочу быть более «массив типа» , чтобы быть в состоянии использовать map, filterи так далее, то я его включить. Простая итерация с использованием forEachне требуется splice.
Denys Séguret
11

Попробуйте, это должно сработать:

<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>
Вайбхав Джайн
источник
0

если вы хотите получить доступ к идентификатору каждого элемента определенного класса, вы можете сделать следующее:

    Array.from(document.getElementsByClassName('myClass')).forEach(function(element) {
        console.log(element.id);
    });
Nelles
источник