Цикл for для элементов HTMLCollection
Я пытаюсь получить идентификаторы всех элементов в HTMLCollectionOf
. Я написал следующий код:
var list = document.getElementsByClassName("events");
console.log(list[0].id);
for (key in list) {
console.log(key.id);
}
Но я получил следующий вывод в консоли:
event1
undefined
Это не то, что я ожидал. Почему второй вывод в консоли — это undefined
, а первый вывод — event1
?
5 ответ(ов)
В ответ на ваш вопрос: вы неправильно используете конструкцию for/in
. В вашем коде key
является индексом. Чтобы получить значение из псевдомассива, вам нужно делать list[key]
, а чтобы получить id
, следует использовать list[key].id
. Однако, в первую очередь, вы не должны использовать for/in
для итерации по элементам.
Резюме (добавлено в декабре 2018)
Никогда не используйте for/in
для итерации по nodeList
или HTMLCollection
. Причины, по которым следует это избегать, описаны ниже.
Все современные браузеры (Safari, Firefox, Chrome, Edge) поддерживают итерацию по DOM-спискам, таким как nodeList
или HTMLCollection
, с помощью конструкции for/of
.
Вот пример:
var list = document.getElementsByClassName("events");
for (let item of list) {
console.log(item.id);
}
Если вам нужно поддерживать старые браузеры (включая Internet Explorer), вы можете использовать следующий код:
var list = document.getElementsByClassName("events");
for (var i = 0; i < list.length; i++) {
console.log(list[i].id); // второй вывод в консоль
}
Объяснение, почему не следует использовать for/in
for/in
предназначен для итерации по свойствам объекта. Это означает, что он вернет все итерируемые свойства объекта. Хотя может показаться, что он работает для массива (возвращая элементы массива или элементы псевдомассива), на самом деле он может вернуть и другие свойства объекта, которые не ожидаются от элементов, похожих на массив. Например, и у объекта HTMLCollection
, и у объекта nodeList
могут быть другие свойства, которые будут возвращены при использовании for/in
. Я только что протестировал это в Chrome, и итерация таким образом выдаст элементы списка (индексы 0, 1, 2 и т.д.), но также вернет свойства length
и item
. Итерация с помощью for/in
просто не будет работать для HTMLCollection
.
Смотрите http://jsfiddle.net/jfriend00/FzZ2H/ для примера того, почему вы не можете использовать for/in
с HTMLCollection
.
В Firefox ваша итерация с for/in
вернет такие элементы (все итерируемые свойства объекта):
0
1
2
item
namedItem
@@iterator
length
Надеюсь, теперь вы понимаете, почему стоит использовать for (var i = 0; i < list.length; i++)
, чтобы в итерации получать только 0
, 1
и 2
.
Эволюция поддержки итерации NodeList и HTMLCollection в браузерах
Ниже представлена эволюция возможностей итерации в браузерах с 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 for/of
с NodeList
и 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. Это возможно благодаря прикреплению итератора массива к прототипам NodeList
и HTMLCollection
, что позволяет использовать итерацию с помощью for/of
.
Работающий пример: 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
в текущих версиях Edge.
Я также обновил вышеупомянутый jsFiddle, чтобы он тестировал отдельно HTMLCollection
и nodeList
, и учитывал вывод в самом jsFiddle.
Четвертое обновление для ES6 в марте 2018
Согласно 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 Fall 2018) теперь можно итерировать как HTMLCollection
, так и NodeList
с помощью for/of
в Edge.
Таким образом, теперь все современные браузеры поддерживают итерацию for/of
как для объектов HTMLCollection
, так и для NodeList
.
В ES6 вы можете преобразовать коллекции в массивы с помощью синтаксиса расширения [...collection]
или метода Array.from(collection)
. Это может быть полезно, когда вы хотите применять методы массива, такие как forEach
, к элементам коллекции.
Например, если у вас есть коллекция DOM-элементов, вы можете сделать следующее:
let someCollection = document.querySelectorAll(someSelector);
[...someCollection].forEach(someFn);
// или
Array.from(someCollection).forEach(someFn);
Вот конкретный пример:
let navDoms = document.getElementsByClassName('nav-container');
Array.from(navDoms).forEach(function(navDom) {
// здесь реализуйте необходимые операции с navDom
});
Используя Array.from()
, вы можете легко и эффективно перебрать элементы коллекции.
Вы можете добавить эти две строки кода:
HTMLCollection.prototype.forEach = Array.prototype.forEach;
NodeList.prototype.forEach = Array.prototype.forEach;
HTMLCollection возвращается методами getElementsByClassName и getElementsByTagName.
NodeList возвращается методом querySelectorAll.
С помощью этого кода вы сможете использовать forEach:
var selections = document.getElementsByClassName('myClass');
/* альтернатива:
var selections = document.querySelectorAll('.myClass');
*/
selections.forEach(function(element, i) {
// выполняйте необходимые действия
});
Теперь, даже если вы используете HTMLCollection или NodeList, вы сможете удобно итерироваться по элементам с помощью метода forEach.
Вариантом использования Array.from
является применение Array.prototype.forEach.call
. Это позволяет вам итерироваться по структурам, которые не являются массивами, например по HTMLCollection
.
Вот пример с forEach
:
Array.prototype.forEach.call(htmlCollection, i => {
console.log(i);
});
А для метода map
можно сделать следующее:
const resultArray = Array.prototype.map.call(htmlCollection, i => {
// Какой-то код для обработки элемента
return i; // Возвращаем элемент для нового массива
});
Этот подход позволяет создавать массив из HTMLCollection
и обрабатывать его элементы аналогично тому, как это делает Array.from
.
У меня возникла проблема с использованием forEach
в IE 11 и Firefox 49.
Я нашел обходной путь, который выглядит так:
Array.prototype.slice.call(document.getElementsByClassName("events")).forEach(function (key) {
console.log(key.id);
});
Этот код преобразует коллекцию элементов, полученных с помощью getElementsByClassName
, в обычный массив с помощью Array.prototype.slice
. Таким образом, вы сможете использовать forEach
без проблем в старых браузерах.
Как узнать, какой элемент DOM имеет фокус?
Как определить, виден ли элемент DOM в текущей области просмотра?
Как выбрать элемент по имени с помощью jQuery?
Удаление всех дочерних элементов узла DOM в JavaScript
Создание нового DOM-элемента из HTML-строки с использованием встроенных методов DOM или Prototype