Для циклу для елементів HTMLCollection


405

Я намагаюся встановити ідентифікатор всіх елементів у an HTMLCollectionOf . Я написав наступний код:

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

Але в консолі я отримав такий вихід:

event1
undefined

що не те, чого я очікував. Чому вихід другої консолі, undefinedале перший вихід консолі event1?


23
чому в заголовку написано "передбачити", коли питання стосується слова "for ..." Я прийшов сюди випадково, коли гуглив.
mxt3

@ mxt3 Ну, на моє розуміння, це був анолог для кожного циклу на Java (працював так само).

1
@ mxt3 Я думав те саме! Але прочитавши прийняту відповідь, я знайшов цей рядок, який вирішив мою проблему передбачення, використовуючи Array.from ():Array.from(document.getElementsByClassName("events")).forEach(function(item) {
HoldOffHunger

Відповіді:


839

У відповідь на оригінальне запитання ви використовуєте for/inнеправильно. У вашому коді keyє індекс. Отже, щоб отримати значення з псевдомасиву, вам доведеться зробити list[key]і отримати ідентифікатор, який ви зробили б list[key].id. Але, for/inв першу чергу, не варто цього робити .

Підсумок (додано у грудні 2018 р.)

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

Всі останні версії сучасних браузерів (Safari, Firefox, Chrome, краю) все підтримують 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 construct з a NodeListі an 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, так що при / для ітерацій він використовує ітератор масиву для ітерації.

Робоча демонстрація: 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як in, document.getElementsByClassName()тому вам доведеться вручну призначити ітератор, щоб використовувати його в Edge для HTMLCollection. Загальна загадка, чому вони виправили один тип колекції, а не інший. Але ви можете принаймні використовувати результат синтаксису document.querySelectorAll()ES6 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приходитиме до краю 18 осені 2018 року.

Шосте оновлення для ES6 у листопаді 2018 року

Я можу підтвердити, що за допомогою Microsoft Edge v18 (що включено в Осіннє оновлення Windows 2018) тепер ви можете переглядати як HTMLCollection, так і NodeList з для / в Edge.

Отже, тепер усі сучасні браузери містять вбудовану підтримку for/ofітерації як об’єктів HTMLCollection, так і NodeList.


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

Видатна відповідь, дивовижна підтримка оновлення .. Дякую.
козаччина

79

Ви не можете використовувати for/ inon NodeListабо HTMLCollections. Однак ви можете використовувати деякі Array.prototypeметоди, якщо ви .call()їх і проходите в NodeListабо HTMLCollectionякthis .

Тому розгляньте наступне як альтернативу циклу jfriend00for :

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

Є хороша стаття про MDN, яка висвітлює цю методику. Зверніть увагу на їх попередження про сумісність браузера:

[...] передача хост-об'єкта (наприклад, а 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.


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].forEachзамість того, щоб робитиrow.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
});

Ця відповідь здається настільки ефективною. Який улов?
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);
    }

Чудове рішення для 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) });

тощо.


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);
});

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/isissue/detail?id=401699

Оновлення: Дивіться коментар Expenzor нижче: Це було виправлено станом на квітень 2016 року. Вам не потрібно додавати HTMLCollection.prototype [Symbol.iterator] = Array.prototype [Symbol.iterator]; для повторення HTMLCollection з ... для


4
Це було виправлено в квітні 2016 року Вам не потрібно , щоб додати HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];до ітерації до HTMLCollectionз for...of.
Expenzor

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);
    }
  }
}

2

Простий спосіб вирішення, яким я завжди користуюся

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

Після цього ви можете запустити будь-які бажані методи масиву для вибору

listArr.map(item => console.log(item.id))
listArr.forEach(item => console.log(item.id))
listArr.reverse()

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
}

5
FYI, дивіться мою відповідь, чому це не працює належним чином. for (key in list)Повертатиме кілька властивостей HTMLCollection, які не повинні бути елементи колекції.
jfriend00
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.