Для цикла для элементов HTMLCollection

406

Я пытаюсь установить получить идентификатор всех элементов в HTMLCollectionOf. Я написал следующий код:

var list = document.getElementsByClassName("events");
console.log(list[0].id);
for (key in list) {
    console.log(key.id);
}

Но я получил следующий вывод в консоли:

event1
undefined

это не то, что я ожидал. Почему второй вывод консоли, undefinedа первый вывод консоли event1?

нейрон
источник
23
почему в заголовке написано "foreach", когда вопрос о for ... in? Я пришел сюда случайно, когда гуглил.
mxt3
@ mxt3 Ну, в моем понимании, это был анолог цикла for-each в Java (работал так же).
1
@ mxt3 Я тоже так думал! Но после прочтения принятого ответа я нашел эту строку, которая решила мою проблему foreach, используя Array.from ():Array.from(document.getElementsByClassName("events")).forEach(function(item) {
HoldOffHunger

Ответы:

838

В ответ на оригинальный вопрос, вы используете for/inнеправильно. В вашем коде keyесть индекс. Итак, чтобы получить значение из псевдомассива, вам нужно сделать, list[key]а чтобы получить идентификатор, вам нужно list[key].id. Но вы не должны делать это с for/inсамого начала.

Резюме (добавлено в декабре 2018 г.)

Никогда не используйте for/inдля итерации nodeList или HTMLCollection. Причины, чтобы избежать этого, описаны ниже.

Все последние версии современных браузеров (Safari, Firefox, Chrome, Edge) поддерживают for/ofитерацию в списках DOM, таких как nodeListили HTMLCollection.

Вот пример:

var list = document.getElementsByClassName("events");
for (let item of list) {
    console.log(item.id);
}

Чтобы включить старые браузеры (включая такие, как IE), это будет работать везде:

var list= document.getElementsByClassName("events");
for (var i = 0; i < list.length; i++) {
    console.log(list[i].id); //second console output
}

Пояснение, почему вы не должны использовать for/in

for/inпредназначен для итерации свойств объекта. Это означает, что он вернет все итерируемые свойства объекта. Хотя может показаться, что он работает для массива (возвращая элементы массива или элементы псевдомассива), он также может возвращать другие свойства объекта, отличные от ожидаемых от элементов, подобных массиву. И, угадайте, что, HTMLCollectionи nodeListобъект или оба могут иметь другие свойства, которые будут возвращены с for/inитерацией. Я просто попытался это в Chrome и итерация его так , как вы итерация будет извлекать элементы в списке (индексы 0, 1, 2, и т.д ...), но также будет получать lengthи itemсвойства. for/inИтерация просто не будет работать для HTMLCollection.


Смотрите http://jsfiddle.net/jfriend00/FzZ2H/ почему вы не можете перебирать в HTMLCollection с for/in.

В Firefox ваша for/inитерация будет возвращать эти элементы (все итерируемые свойства объекта):

0
1
2
item
namedItem
@@iterator
length

Надеюсь, теперь вы можете понять, почему вы хотите использовать for (var i = 0; i < list.length; i++)вместо этого, чтобы вы просто получили 0, 1и 2в вашей итерации.


Ниже приводится эволюция эволюции браузеров за период 2015-2018 гг., Которая дает вам дополнительные способы итерации. В современных браузерах ничего из этого не требуется, поскольку вы можете использовать опции, описанные выше.

Обновление для ES6 в 2015 году

В ES6 добавлено то, Array.from()что преобразовать массивоподобную структуру в реальный массив. Это позволяет прямо перечислять список следующим образом:

"use strict";

Array.from(document.getElementsByClassName("events")).forEach(function(item) {
   console.log(item.id);
});

Рабочая демонстрация (в Firefox, Chrome и Edge на апрель 2016 г.): https://jsfiddle.net/jfriend00/8ar4xn2s/


Обновление для ES6 в 2016 году

Теперь вы можете использовать ES6 для / of конструкции с a NodeListи a HTMLCollection, просто добавив это в ваш код:

NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];

Затем вы можете сделать:

var list = document.getElementsByClassName("events");
for (var item of list) {
    console.log(item.id);
}

Это работает в текущей версии Chrome, Firefox и Edge. Это работает, потому что он присоединяет итератор Array к прототипам NodeList и HTMLCollection, так что, когда for / of итерирует их, он использует итератор Array для их итерации.

Рабочая демонстрация: http://jsfiddle.net/jfriend00/joy06u4e/ .


Второе обновление для ES6 в декабре 2016 года

По состоянию на декабрь 2016 года Symbol.iteratorподдержка была встроена в Chrome v54 и Firefox v50, поэтому приведенный ниже код работает сам по себе. Он еще не встроен в Edge.

var list = document.getElementsByClassName("events");
for (let item of list) {
    console.log(item.id);
}

Рабочая демонстрация (в Chrome и Firefox): http://jsfiddle.net/jfriend00/3ddpz8sp/

Третье обновление для ES6 в декабре 2017 года

По состоянию на декабрь 2017 года эта возможность работает в Edge 41.16299.15.0 для « nodeListкак в» document.querySelectorAll(), но не для « HTMLCollectionкак в», document.getElementsByClassName()поэтому вам нужно вручную назначить итератор, чтобы использовать его в Edge для HTMLCollection. Это загадка, почему они исправляют один тип коллекции, а не другой. Но вы можете по крайней мере использовать результат document.querySelectorAll()с for/ofсинтаксисом ES6 в текущих версиях Edge сейчас.

Я также обновил вышеупомянутый jsFiddle, чтобы он тестировал HTMLCollectionи nodeListотдельно, и захватывал выходные данные в самом jsFiddle.

Четвертое обновление для ES6 в марте 2018 года

Per mesqueeeb, Symbol.iteratorподдержка была встроена в Safari, так что вы можете использовать for (let item of list)для любой document.getElementsByClassName()илиdocument.querySelectorAll() .

Пятое обновление для ES6 в апреле 2018 года

По-видимому, поддержка итерации HTMLCollectionс for/ofбудет приходить в Edge 18 осенью 2018 года.

Шестое обновление для ES6 в ноябре 2018 года

Я могу подтвердить, что с Microsoft Edge v18 (которая включена в Windows Update 2018 года) теперь вы можете перебирать HTMLCollection и NodeList с for / of в Edge.

Итак, теперь все современные браузеры содержат встроенную поддержку for/ofитерации объектов HTMLCollection и NodeList.

jfriend00
источник
1
Спасибо за очень подробные обновления, так как JS обновил. Это помогает новичкам понять этот совет в контексте других статей / постов, которые относятся только к конкретному выпуску JS.
brownmagik352
Отличный ответ, потрясающая поддержка обновлений .. Спасибо.
казак
80

Вы не можете использовать for/ inна NodeListс или HTMLCollectionс. Тем не менее, вы можете использовать некоторые Array.prototypeметоды, если вы .call()их и передать в NodeListили HTMLCollectionкак this.

Таким образом , необходимо учитывать следующее в качестве альтернативы jfriend00 игровой forпетлю :

var list= document.getElementsByClassName("events");
[].forEach.call(list, function(el) {
    console.log(el.id);
});

На MDN есть хорошая статья, которая описывает эту технику. Обратите внимание на предупреждение о совместимости браузера:

[...] передача хост-объекта (например, a NodeList) как thisнативный метод (например, forEach) не гарантирует работу во всех браузерах и, как известно, в некоторых случаях дает сбой.

Таким образом, хотя этот подход удобен, forцикл может быть наиболее совместимым с браузером решением.

Обновление (30 августа 2014 г.): со временем вы сможете использовать ES6 for/of !

var list = document.getElementsByClassName("events");
for (const el of list)
  console.log(el.id);

Это уже поддерживается в последних версиях Chrome и Firefox.

evanrmurphy
источник
1
Очень хорошо! Я использовал эту технику, чтобы получить значения выбранных параметров из <select multiple>. Пример:[].map.call(multiSelect.selectedOptions, function(option) { return option.value; })
XåpplI'-I0llwlg'I -
1
Я искал решение ES2015 для этого, так что спасибо за подтверждение того, что for ... ofработает.
Ричард Тернер
60

В ES6 вы могли бы сделать что-то вроде [...collection], или Array.from(collection),

let someCollection = document.querySelectorAll(someSelector)
[...someCollection].forEach(someFn) 
//or
Array.from(collection).forEach(someFn)

Например:-

    navDoms = document.getElementsByClassName('nav-container');
    Array.from(navDoms).forEach(function(navDom){
     //implement function operations
    });
Мидо
источник
@DanielM думаю, что я сделал, мелкое клонирование массива, как структура
мидо
Я вижу, спасибо - теперь я нашел документацию, которую искал: developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
DanielM
Я всегда использую это, гораздо проще для глаз, чем Array.from, мне просто интересно, имеет ли он значительную производительность или недостатки памяти. Например, если мне нужно [...row.cells].forEachrow.querySelectorAll('td')
перебрать
16

Вы можете добавить эти две строки:

HTMLCollection.prototype.forEach = Array.prototype.forEach;
NodeList.prototype.forEach = Array.prototype.forEach;

HTMLCollection возвращается getElementsByClassName и getElementsByTagName

NodeList возвращается querySelectorAll

Вот так вы можете сделать forEach:

var selections = document.getElementsByClassName('myClass');

/* alternative :
var selections = document.querySelectorAll('.myClass');
*/

selections.forEach(function(element, i){
//do your stuffs
});
mmnl
источник
Этот ответ кажется таким эффективным. В чем подвох?
Peheje
2
Суть в том, что это решение не работает на IE11! Хорошее решение, хотя.
Рахул Габа
2
Обратите внимание, что NodeListуже естьforEach() .
Франклин Ю
7

У меня была проблема с использованием forEach в IE 11, а также Firefox 49

Я нашел обходной путь, как это

Array.prototype.slice.call(document.getElementsByClassName("events")).forEach(function (key) {
        console.log(key.id);
    }
mamosek
источник
Отличное решение для IE11!
Раньше
6

Альтернативой Array.fromявляется использованиеArray.prototype.forEach.call

для каждого: Array.prototype.forEach.call(htmlCollection, i => { console.log(i) });

карта: Array.prototype.map.call(htmlCollection, i => { console.log(i) });

ЭСТ ...

holmberd
источник
6

Нет причин использовать функции es6, чтобы избежать forзацикливания, если вы используете IE9 или выше.

В ES5 есть два хороших варианта. Во-первых, вы можете «одолжить» Array, forEachкак упоминает Эван .

Но даже лучше ...

Использование Object.keys(), что делает есть forEachи фильтры для «собственных свойств» автоматически.

То есть, Object.keysпо сути, эквивалентно делать for... inс a HasOwnProperty, но гораздо более плавно.

var eventNodes = document.getElementsByClassName("events");
Object.keys(eventNodes).forEach(function (key) {
    console.log(eventNodes[key].id);
});
Ruffin
источник
5

По состоянию на март 2016 года в Chrome 49.0 for...ofработает для HTMLCollection:

this.headers = this.getElementsByTagName("header");

for (var header of this.headers) {
    console.log(header); 
}

Смотрите здесь документацию .

Но это работает, только если вы применили следующий обходной путь перед использованием for...of:

HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];

То же самое необходимо использовать for...ofс NodeList:

NamedNodeMap.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];

Я верю / надеюсь for...ofскоро будет работать без вышеуказанного обходного пути. Открытый вопрос здесь:

https://bugs.chromium.org/p/chromium/issues/detail?id=401699

Обновление: см. Комментарий Expenzor ниже: Это было исправлено по состоянию на апрель 2016 года. Вам не нужно добавлять HTMLCollection.prototype [Symbol.iterator] = Array.prototype [Symbol.iterator]; перебрать HTMLCollection с for ... of

MarcG
источник
4
Это было исправлено по состоянию на апрель 2016 года. Вам не нужно добавлять, HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];чтобы перебрать HTMLCollectionс for...of.
Счет
3

По краю

if(!NodeList.prototype.forEach) {
  NodeList.prototype.forEach = function(fn, scope) {
    for(var i = 0, len = this.length; i < len; ++i) {
      fn.call(scope, this[i], i, this);
    }
  }
}
Tiago Pertile
источник
2

Простой обходной путь, который я всегда использую

let list = document.getElementsByClassName("events");
let listArr = Array.from(list)

После этого вы можете запустить любые нужные методы Array на выбор

listArr.map(item => console.log(item.id))
listArr.forEach(item => console.log(item.id))
listArr.reverse()
Creeptosis
источник
1

если вы используете более старые версии ES (например, ES5), вы можете использовать as any:

for (let element of elementsToIterate as any) {
      console.log(element);
}
Алон Гулдман
источник
0

Вы хотите изменить это на

var list= document.getElementsByClassName("events");
console.log(list[0].id); //first console output
for (key in list){
    console.log(list[key].id); //second console output
}
Andy897
источник
5
К вашему сведению, посмотрите мой ответ, почему это не будет работать должным образом. for (key in list)Будет возвращать несколько свойств HTMLCollection, которые не должны быть элементы коллекции.
jfriend00