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(по крайней мере, в современных браузерах. Старые браузеры возвращали a 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
Примечание: без переноса, как это было предложено (Бабель), это НЕ совместимо в 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 ()

icl7126
источник
4
Помните о снижении производительности по сравнению с getElementByClassName
Сабольч Палл
3
Потеря производительности незначительна по сравнению с другими более интенсивными задачами, такими как изменение DOM. Если я выполню 60000 из них за 1 миллисекунду , я почти уверен, что это не будет проблемой для любого разумного использования :)
icl7126
1
Вы связали неправильный тест Вот правильная мера: that.net/Benchmarks/Show/4076/0/… Просто запустил его на моем бюджетном телефоне, получил 160 к / с против 380 к / с. Так как вы упомянули о манипуляциях с DOM, вот что также измерило: that.net/Benchmarks/Show/5705/0/… Получил 50k / s против 130k / s. Как вы видите, манипулировать DOM еще медленнее, вероятно, из-за того, что NodeList статичен (как упоминалось другими). В большинстве случаев все еще можно пренебречь, но тем не менее почти в 3 раза медленнее.
Сабольч Палл
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.

james.garriss
источник
9

Результатом getElementsByClassName()является не массив, а объект массива . В частности это называется HTMLCollection, не путать с NodeList( который имеет свой собственный forEach()метод ).

Один простой способ с ES2015 преобразовать подобный массиву объект для использования с, Array.prototype.forEach()который еще не был упомянут, состоит в том, чтобы использовать оператор распространения или синтаксис распространения :

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

[...elementsArray].forEach((element, index, array) => {
    // do something
});
Kloptikus
источник
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 интерфейс, который является итеративным, будет иметь свойства «entry», «forEach», «keys», «values» и @@ 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 Попробуй 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]);
gildniy
источник
0

getElementsByClassNameвозвращает HTMLCollection в современных браузерах.

который является массивоподобным объектом, похожим на аргументы, итеративный по for...ofциклу, смотрите ниже, что MDN doc говорит об этом:

Оператор for ... of создает цикл, повторяющийся над повторяемыми объектами , включая: встроенные объекты String, Array, Array-like (например, arguments или NodeList), TypedArray, Map, Set и определяемые пользователем итерируемые элементы. Он вызывает пользовательский итерационный хук с инструкциями, которые должны быть выполнены для значения каждого отдельного свойства объекта.

пример

for (let element of getElementsByClassName("classname")){
   element.style.display="none";
}
Харицинь Гохил
источник
Не так, согласно Typescript:error TS2488: Type 'HTMLCollectionOf<Element>' must have a '[Symbol.iterator]()' method that returns an iterator.
Черепахи милые
@TurtlesAreCute, здесь OP использует javascript, а не машинописный текст, и я ответил в соответствии с рекомендациями vanilla js, поэтому в машинописном тексте это может быть другим решением проблемы.
Харицин Гохил
@TurtlesAreCute, кстати, он также работает и в машинописном тексте, но вы должны упомянуть правильный тип переменной, который содержит элемент определенного класса CSS, поэтому он может привести его соответственно, подробнее см. Этот ответ .
Харицин Гохил
0

Вот тест, который я создал на jsperf: https://jsperf.com/vanillajs-loop-through-elements-of-class

Самая популярная версия в Chrome и Firefox - это старый добрый цикл for в сочетании с 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';
    }
);
StefanSL
источник