Dla pętli dla elementów HTMLCollection


405

Próbuję ustawić get id wszystkich elementów w pliku HTMLCollectionOf. Napisałem następujący kod:

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

Ale dostałem następujące dane wyjściowe w konsoli:

event1
undefined

czego się nie spodziewałem. Dlaczego jest to wyjście drugiej konsoli, undefinedale pierwsze wyjście konsoli event1?


23
dlaczego tytuł mówi „foreach”, gdy pytanie dotyczy oświadczenia for… Przyjechałem tu przypadkowo, kiedy googlowałem.
mxt3

@ mxt3 Cóż, moim zdaniem, był to anolog dla każdej pętli w Javie (działał tak samo).

1
@ mxt3 Myślałem o tym samym! Ale po przeczytaniu zaakceptowanej odpowiedzi znalazłem ten wiersz, który rozwiązał mój problem Foreach, za pomocą Array.from ():Array.from(document.getElementsByClassName("events")).forEach(function(item) {
HoldOffHunger

Odpowiedzi:


839

W odpowiedzi na oryginalne pytanie używasz for/innieprawidłowo. W twoim kodzie keyjest indeks. Tak więc, aby uzyskać wartość z pseudo-tablicy, musiałbyś to zrobić, list[key]a żeby uzyskać identyfikator, zrobiłbyś to list[key].id. Ale for/inprzede wszystkim nie powinieneś tego robić .

Podsumowanie (dodano w grudniu 2018 r.)

Nigdy nie używaj for/indo iteracji nodeList lub HTMLCollection. Powody, dla których można tego uniknąć, opisano poniżej.

Wszystkie najnowsze wersje nowoczesnych przeglądarek (Safari, Firefox, Chrome, Krawędź) wszelkie wsparcie for/ofiteracja na DOM wymienia takie nodeListlub HTMLCollection.

Oto przykład:

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

Aby uwzględnić starsze przeglądarki (w tym takie jak IE), będzie to działać wszędzie:

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

Wyjaśnienie, dlaczego nie powinieneś używać for/in

for/insłuży do iteracji właściwości obiektu. Oznacza to, że zwróci wszystkie iterowalne właściwości obiektu. Chociaż może się wydawać, że działa dla tablicy (zwraca elementy tablicy lub elementy pseudo-tablicy), może również zwracać inne właściwości obiektu, które nie są tym, czego oczekujesz od elementów podobnych do tablicy. I zgadnij, co, HTMLCollectionalbo nodeListobiekt może mieć inne właściwości, które zostaną zwrócone z for/initeracją. Właśnie próbowałem tego w Chrome i iteracja w taki sposób, w jaki iterowałeś, pobierze elementy z listy (indeksy 0, 1, 2 itd.), Ale także pobierze właściwości lengthi item. for/inIteracja po prostu nie będzie działać na HTMLCollection.


Zobacz http://jsfiddle.net/jfriend00/FzZ2H/, aby dowiedzieć się, dlaczego nie możesz iterować kolekcji HTMLCollection for/in.

W przeglądarce Firefox twoja for/initeracja zwróciłaby te elementy (wszystkie iterowalne właściwości obiektu):

0
1
2
item
namedItem
@@iterator
length

Mam nadzieję, że teraz można zobaczyć, dlaczego chcesz używać for (var i = 0; i < list.length; i++)zamiast tak po prostu dostać 0, 1a 2w iteracji.


Poniżej znajduje się ewolucja ewolucji przeglądarek w okresie 2015-2018, co daje dodatkowe możliwości iteracji. Żadne z nich nie są teraz potrzebne w nowoczesnych przeglądarkach, ponieważ możesz skorzystać z opcji opisanych powyżej.

Aktualizacja dla ES6 w 2015 roku

Dodano do ES6 to, Array.from()że konwertuje strukturę podobną do tablicy na rzeczywistą tablicę. To pozwala wyliczyć listę bezpośrednio w ten sposób:

"use strict";

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

Działające demo (w Firefox, Chrome i Edge od kwietnia 2016 r.): Https://jsfiddle.net/jfriend00/8ar4xn2s/


Aktualizacja dla ES6 w 2016 roku

Możesz teraz używać ES6 dla / z konstruktem za pomocą a NodeListi HTMLCollectionpo prostu dodając to do swojego kodu:

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

Następnie możesz wykonać:

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

Działa to w bieżącej wersji Chrome, Firefox i Edge. Działa to, ponieważ dołącza iterator Array zarówno do prototypów NodeList, jak i HTMLCollection, dzięki czemu podczas ich iteracji używa iteratora Array do iteracji.

Działające demo: http://jsfiddle.net/jfriend00/joy06u4e/ .


Druga aktualizacja ES6 w grudniu 2016 r

Od grudnia 2016 r. Symbol.iteratorObsługa Chrome jest wbudowana w Chrome v54 i Firefox v50, więc poniższy kod działa sam. Nie jest jeszcze wbudowany w Edge.

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

Działające demo (w Chrome i Firefox): http://jsfiddle.net/jfriend00/3ddpz8sp/

Trzecia aktualizacja ES6 w grudniu 2017 r

Od grudnia 2017 r. Ta funkcja działa w Edge 41.16299.15.0 dla wersji nodeListas document.querySelectorAll(), ale nie dla wersji HTMLCollectionas, document.getElementsByClassName()więc musisz ręcznie przypisać iterator do użycia w Edge dla wersji HTMLCollection. Jest całkowitą tajemnicą, dlaczego naprawili jeden typ kolekcji, ale nie drugi. Ale teraz możesz przynajmniej użyć wyniku document.querySelectorAll()ze for/ofskładnią ES6 w bieżących wersjach Edge.

Ja również zaktualizowane wyżej jsFiddle więc testuje zarówno HTMLCollectioni nodeListosobno i oddaje moc w samej jsFiddle.

Czwarta aktualizacja ES6 w marcu 2018 r

W przypadku mesqueeeb Symbol.iteratorobsługa Safari została również wbudowana, dzięki czemu można korzystać for (let item of list)z jednego document.getElementsByClassName()lub drugiego document.querySelectorAll().

Piąta aktualizacja ES6 w kwietniu 2018 r

Najwyraźniej wsparcie dla iteracji HTMLCollectionz for/ofpojawi się na Edge 18 jesienią 2018 r.

Szósta aktualizacja ES6 w listopadzie 2018 r

Mogę potwierdzić, że dzięki Microsoft Edge v18 (który jest zawarty w Windows Update Fall 2018), możesz teraz iterować zarówno HTMLCollection, jak i NodeList z for / of w Edge.

Tak więc teraz wszystkie nowoczesne przeglądarki zawierają natywną obsługę for/ofiteracji zarówno obiektów HTMLCollection, jak i NodeList.


1
Dziękujemy za bardzo szczegółowe aktualizacje w miarę aktualizacji JS. Pomaga to początkującym zrozumieć tę radę w kontekście innych artykułów / postów, które dotyczą tylko określonej wersji JS.
brownmagik352

Znakomita odpowiedź, niesamowite wsparcie aktualizacji. Dziękuję.
Kozak

79

Nie możesz używać for/ inon NodeLists lub HTMLCollections. Możesz jednak użyć niektórych Array.prototypemetod, o ile .call()je zastosujesz i przekazać w NodeListlub HTMLCollectionas this.

Zastanów sięfor zatem, czy nie jest to alternatywa dla pętli jfriend00 :

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

Istnieje dobry artykuł na temat MDN, który dotyczy tej techniki. Zwróć jednak uwagę na ich ostrzeżenie dotyczące zgodności przeglądarki:

[...] przekazanie obiektu hosta (takiego jak a NodeList) thisdo metody rodzimej (takiej jak forEach) nie jest gwarantowane do działania we wszystkich przeglądarkach i wiadomo, że w niektórych przypadkach zawiedzie.

Chociaż takie podejście jest wygodne, forpętla może być najbardziej kompatybilnym rozwiązaniem dla przeglądarki.

Aktualizacja (30 sierpnia 2014 r.): W końcu będziesz mógł używać ES6 for/of !

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

Jest już obsługiwany w najnowszych wersjach Chrome i Firefox.


1
Bardzo dobrze! Użyłem tej techniki, aby uzyskać wartości wybranych opcji z <select multiple>. Przykład:[].map.call(multiSelect.selectedOptions, function(option) { return option.value; })
XåpplI'-I0llwlg'I -

1
Szukałem rozwiązania ES2015, więc dziękuję za potwierdzenie, że for ... ofdziała.
Richard Turner

60

W ES6, można zrobić coś takiego [...collection], lub Array.from(collection),

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

Na przykład:-

    navDoms = document.getElementsByClassName('nav-container');
    Array.from(navDoms).forEach(function(navDom){
     //implement function operations
    });

@DanielM zgadnij, że to, co zrobiłem, to płytkie klonowanie struktury podobnej do tablicy
połowie

Rozumiem, dziękuję - teraz znalazłem dokumentację, której szukałem: developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
DanielM

Zawsze używam tego, o wiele łatwiejszego dla oczu niż Array. Z, po prostu zastanawiam się, czy ma to znaczące wady wydajności lub pamięci. Na przykład, jeśli muszę iterować komórki wiersza tabeli, używam [...row.cells].forEachzamiast robićrow.querySelectorAll('td')
Mojimi,

16

możesz dodać te dwie linie:

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

HTMLCollection jest zwracany przez getElementsByClassName i getElementsByTagName

NodeList jest zwracany przez querySelectorAll

W ten sposób możesz zrobić forEach:

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

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

selections.forEach(function(element, i){
//do your stuffs
});

Ta odpowiedź wydaje się tak skuteczna. Jaki jest haczyk?
Peheje,

2
Problem polega na tym, że to rozwiązanie nie działa na IE11! Jednak dobre rozwiązanie.
Rahul Gaba


7

Miałem problem z korzystaniem z forEach w IE 11, a także Firefox 49

Znalazłem takie obejście

Array.prototype.slice.call(document.getElementsByClassName("events")).forEach(function (key) {
        console.log(key.id);
    }

Świetne rozwiązanie dla IE11! Kiedyś była to powszechna technika ...
mb21,

6

Alternatywą Array.fromjest użycieArray.prototype.forEach.call

dla każdego: Array.prototype.forEach.call(htmlCollection, i => { console.log(i) });

mapa: Array.prototype.map.call(htmlCollection, i => { console.log(i) });

ect ...


6

Nie ma powodu, aby używać funkcji es6, aby uniknąć forpętli, jeśli korzystasz z IE9 lub nowszej wersji.

W ES5 są dwie dobre opcje. Po pierwsze, możesz „pożyczyć” Array, forEachjak wspomina Evan .

Ale jeszcze lepiej ...

Stosowanie Object.keys(), które nie mają forEachi filtry do „własnych” Automatycznie właściwości.

Oznacza to, że Object.keysjest zasadniczo równoważne z robieniem for... inz HasOwnProperty, ale jest znacznie płynniejsze.

var eventNodes = document.getElementsByClassName("events");
Object.keys(eventNodes).forEach(function (key) {
    console.log(eventNodes[key].id);
});

5

Od marca 2016 r. W przeglądarce Chrome 49.0 for...ofdziała HTMLCollection:

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

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

Zobacz tutaj dokumentację .

Ale działa to tylko wtedy, gdy zastosujesz następujące obejście przed użyciem for...of:

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

To samo jest konieczne do korzystania for...ofz NodeList:

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

Wierzę / mam nadzieję, for...ofże wkrótce zadziała bez powyższego obejścia. Otwarty problem jest tutaj:

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

Aktualizacja: patrz komentarz Expenzora poniżej: Zostało to naprawione w kwietniu 2016 r. Nie trzeba dodawać HTMLCollection.prototype [Symbol.iterator] = Array.prototype [Symbol.iterator]; iterować przez HTMLCollection z dla ... z


4
Zostało to naprawione w kwietniu 2016 r. Nie trzeba dodawać HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];do iteracji w ciągu HTMLCollectionz for...of.
Expenzor,

3

Na krawędzi

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

Proste obejście, którego zawsze używam

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

Następnie możesz uruchomić dowolne pożądane metody Array dla zaznaczenia

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

1

jeśli używasz starszych wersji ES (na przykład ES5), możesz użyć as any:

for (let element of elementsToIterate as any) {
      console.log(element);
}

0

Chcesz to zmienić na

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
Do Twojej wiadomości, zobacz moją odpowiedź, dlaczego to nie działa poprawnie. for (key in list)Powróci kilka właściwości HTMLCollection, które nie mają być elementy w kolekcji.
jfriend00
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.