JS: ітерація над результатом getElementsByClassName за допомогою Array.forEach


240

Я хочу повторити деякі елементи DOM, я роблю це:

document.getElementsByClassName( "myclass" ).forEach( function(element, index, array) {
  //do stuff
});

але я отримую помилку:

document.getElementsByClassName ("myclass"). forEach не є функцією

Я використовую Firefox 3, тому я знаю, що обидва є getElementsByClassNameі Array.forEachє. Це добре працює:

[2, 5, 9].forEach( function(element, index, array) {
  //do stuff
});

Це результат getElementsByClassNameмасиву? Якщо ні, то що це?

Відповіді:


384

Ні. Як зазначено в DOM4 , це HTMLCollection(принаймні, у сучасних браузерах. Старіші браузери повернулися а NodeList).

У всіх сучасних браузерах (майже будь-який інший IE <= 8) ви можете викликати forEachметод Array , передаючи йому список елементів (будь то HTMLCollectionчи NodeList) як thisзначення:

var els = document.getElementsByClassName("myclass");

Array.prototype.forEach.call(els, function(el) {
    // Do stuff here
    console.log(el.tagName);
});

// Or
[].forEach.call(els, function (el) {...});

Якщо ви щасливі, що зможете використовувати ES6 (тобто ви можете сміливо ігнорувати Internet Explorer або використовуєте транспілер ES5), ви можете використовувати Array.from:

Array.from(els).forEach((el) => {
    // Do stuff here
    console.log(el.tagName);
});

29
Не потрібно спочатку конвертувати його в масив. Просто використовуйте [].forEach.call(elsArray, function () {...}).
кей - SE є зло

1
Це НЕ NodeList. Це об’єкт, схожий на масив. Я навіть не думаю, що він має тип екземпляра. querySelectorAllХоча метод повертає NodeList.
Максим Ві.

2
@MaksimVi. Ви абсолютно праві: DOM4 вказує, що document.getElementsByClassName()повинен повернути HTMLCollection(що дуже схоже, але не NodeList). Дякуємо, що вказали на помилку.
Тім Даун

@MaksimVi. Цікаво, чи змінилося це в якийсь момент. Я зазвичай перевіряю ці речі.
Тім Даун

1
@TimDown, Дякую за HTMLCollectionпораду. Тепер я нарешті можу використовувати HTMLCollection.prototype.forEach = Array.prototype.forEach;в своєму коді.
Максим Ві.

70

Ви можете використовувати Array.fromдля перетворення колекції в масив, який чистіше, ніж Array.prototype.forEach.call:

Array.from(document.getElementsByClassName("myclass")).forEach(
    function(element, index, array) {
        // do stuff
    }
);

У старих браузерах, які не підтримують Array.from, потрібно використовувати щось на зразок Babel.


ES6 також додає цей синтаксис:

[...document.getElementsByClassName("myclass")].forEach(
    (element, index, array) => {
        // do stuff
    }
);

Деструструктура відпочинку за допомогою ...всіх об'єктів, схожих на масив, не лише самих масивів, тоді гарний синтаксис старого масиву використовується для побудови масиву зі значень.


У той час як альтернативна функція querySelectorAll(яка getElementsByClassNameніби не застаріла) повертає колекцію, яка є forEachвродженою, інші методи на кшталт mapабо filterвідсутні, тому цей синтаксис все ще корисний:

[...document.querySelectorAll(".myclass")].map(
    (element, index, array) => {
        // do stuff
    }
);

[...document.querySelectorAll(".myclass")].map(element => element.innerHTML);

6
Примітка: без переливання, як пропонується (Babel), це НЕ сумісне в IE <Edge, Opera, Safari <9, браузер Android, Chrome для Android, і т.д.) Джерело: mozilla dev docs
Sean

30

Або ви можете використовувати, querySelectorAllякий повертає NodeList :

document.querySelectorAll('.myclass').forEach(...)

Підтримується сучасними браузерами (включаючи Edge, але не IE):
чи можу я використовувати querySelectorAll
NodeList.prototype.forEach ()

MDN: Document.querySelectorAll ()


4
Майте на увазі штрафний показник за рахунок getElementByClassName
Szabolcs Páll

3
Штраф за ефективність незначний порівняно з іншими більш інтенсивними завданнями, такими як модифікація DOM. Якщо я виконаю 60 000 з них за 1 мілісекунд , я майже впевнений, що це не буде проблемою для розумного використання :)
icl7126

1
Ви пов’язали неправильний орієнтир. Ось правильний показник ukrethat.net/Benchmarks/Show/4076/0/… Я просто запустив його на своєму низькому класі, отримав 160 к / с проти 380 к / с. Оскільки ви згадали про маніпуляцію з DOM, ось це теж мертво.net /Benchmarks/Show/5705/0/… Отримав 50 к / с проти 130 к / с. Як ви бачите, маніпулювати DOM ще повільніше, ймовірно через статичний NodeList (про що згадують інші). У більшості випадків використання все ще є незначним, але, тим не менше, майже в 3 рази повільніше.
Szabolcs Páll

14

Редагувати: Хоча тип повернення змінився в нових версіях HTML (див. Оновлену відповідь Тіма Дауна), код нижче все ще працює.

Як говорили інші, це NodeList. Ось повний робочий приклад, який ви можете спробувати:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <script>
            function findTheOddOnes()
            {
                var theOddOnes = document.getElementsByClassName("odd");
                for(var i=0; i<theOddOnes.length; i++)
                {
                    alert(theOddOnes[i].innerHTML);
                }
            }
        </script>
    </head>
    <body>
        <h1>getElementsByClassName Test</h1>
        <p class="odd">This is an odd para.</p>
        <p>This is an even para.</p>
        <p class="odd">This one is also odd.</p>
        <p>This one is not odd.</p>
        <form>
            <input type="button" value="Find the odd ones..." onclick="findTheOddOnes()">
        </form>
    </body>
</html>

Це працює в IE 9, FF 5, Safari 5 та Chrome 12 на Win 7.


9

Результатом getElementsByClassName()не є масив, а об’єкт, схожий на масив . Зокрема, це називається an HTMLCollection, не плутати з ним NodeListякого є власний forEach()метод ).

Один простий спосіб з ES2015 для перетворення об’єкта, подібного до масиву, для використання, про Array.prototype.forEach()який ще не було сказано, - це використовувати оператор розповсюдження або синтаксис спред :

const elementsArray = document.getElementsByClassName('myclass');

[...elementsArray].forEach((element, index, array) => {
    // do something
});

2
Я вважаю, що це справді правильний спосіб зробити це в сучасних браузерах. Саме для цього було створено синтаксис поширення випадків поширення.
Метт Коростофф


3

Як уже було сказано, getElementsByClassNameповертає HTMLCollection , який визначається як

[Exposed=Window]
interface HTMLCollection {
  readonly attribute unsigned long length;
  getter Element? item(unsigned long index);
  getter Element? namedItem(DOMString name);
};

Раніше деякі браузери натомість повертали NodeList .

[Exposed=Window]
interface NodeList {
  getter Node? item(unsigned long index);
  readonly attribute unsigned long length;
  iterable<Node>;
};

Різниця важлива, оскільки тепер DOM4 визначає NodeList s як ітерабельний.

Згідно з проектом Web IDL ,

Об'єкти, що реалізують інтерфейс, який оголошується ітерабельною підтримкою, повторюється для отримання послідовності значень.

Примітка . У прив'язці до мови ECMAScript інтерфейс, який можна виправити, матиме властивості "записи", "forEach", "ключі", "значення" та @@ iterator на об'єкті прототипу інтерфейсу .

Це означає, що якщо ви хочете використовувати forEach, ви можете використовувати метод DOM, який повертає NodeList , наприклад querySelectorAll.

document.querySelectorAll(".myclass").forEach(function(element, index, array) {
  // do stuff
});

Зауважте, це ще не підтримується широко. Також див. ForEach метод Node.childNodes?


1
Повернення Chrome 49forEach in not a function
Віталій Зданевич,

@VitalyZdanevich Try Chromium 50
Oriol

На Chrome 50 я отримуюdocument.querySelectorAll(...).forEach is not a function
Віталій Зданевич,

@VitalyZdanevich Він працював на Chromium 50 і досі працює на Chromium 53. Можливо, це не вважалося достатньо стабільним, щоб його можна було відправити на Chrome 50.
Oriol


1

Це безпечніший спосіб:

var elements = document.getElementsByClassName("myclass");
for (var i = 0; i < elements.length; i++) myFunction(elements[i]);

0

getElementsByClassNameповертає HTMLCollection в сучасних браузерах.

який є масив як об'єкт схожий на аргументи , який iteratable по for...ofпетлі см нижче , ніж MDN документ говорить про це:

Оператор for ... створює цикл ітерації над ітерабельними об'єктами , включаючи: вбудовані String, Array, Array-подібні об'єкти (наприклад, аргументи або NodeList), TypedArray, Map, Set та визначені користувачем ітерабелі. Він викликає користувацький ітераційний гачок із заявами, які слід виконати для значення кожного окремого властивості об'єкта.

приклад

for (let element of getElementsByClassName("classname")){
   element.style.display="none";
}

Не так, згідно машинопису:error TS2488: Type 'HTMLCollectionOf<Element>' must have a '[Symbol.iterator]()' method that returns an iterator.
Черепахи милі

@TurtlesAreCute, Тут OP використовує JavaScript не typecript, і я відповів згідно з рекомендацією vanilla js, тому в typecript це може бути різним рішенням проблеми.
Харіцин Гохіль

@TurtlesAreCute, До речі, він також працює і в typecript, але ви повинні згадати правильний тип змінної, що містить елемент певного класу css, щоб він міг відповідати, детально див. Цю відповідь .
Харіцин Гохіль

0

Ось тест, який я створив на jsperf: https://jsperf.com/vanillajs-loop-through-elements-of-class

Найбільш розповсюджена версія в Chrome і Firefox - це добра версія для циклу в поєднанні з document.getElementsByClassName:

var elements = document.getElementsByClassName('testClass'), elLength = elements.length;
for (var i = 0; i < elLength; i++) {
    elements.item(i).textContent = 'Tested';
};

У Safari цей варіант є переможцем:

var elements = document.querySelectorAll('.testClass');
elements.forEach((element) => {
    element.textContent = 'Tested';
});

Якщо ви хочете найбільш популярний варіант для всіх браузерів, це може бути саме цей:

var elements = document.getElementsByClassName('testClass');
Array.from(elements).map(
    (element) => {
        return element.textContent = 'Tested';
    }
);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.