Використання forEach для масиву з getElementsByClassName призводить до “TypeError: undefined is not a function”


91

У своєму JSFiddle я просто намагаюся перебирати масив елементів. Масив не є порожнім, як підтверджують оператори журналу. І все-таки заклик до forEachвидає мені (не настільки корисну) помилку "Не спійманий TypeError: undefinedце не функція".

Напевно, я роблю щось дурне; що я роблю не так?

Мій код:

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- це не масив, а a HTMLCollection. Він не має тих самих методів, що і масив. developer.mozilla.org/en-US/docs/Web/API/… . Ось така публікація про це навіть: 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.
Nope,

1
@ Джер - вам слід розглянути відмінності між вбудованими та хост-об'єктами. Перші відповідають ECMA-262, пізніші лише настільки, на скільки бажає господар. DOM має безліч об'єктів, що дозволяють доступ до членів за індексом (document.images, document.forms, form.elements, select.options тощо), переважно на основі інтерфейсу NodeList .
RobG

Відповіді:


162

Це тому, що 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.
Денис Сегурет,

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);
    });
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.