Untuk loop untuk elemen HTMLCollection

406

Saya mencoba mengatur agar mendapatkan id dari semua elemen dalam sebuah HTMLCollectionOf. Saya menulis kode berikut:

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

Tapi saya mendapat output berikut di konsol:

event1
undefined

yang bukan itu yang saya harapkan. Mengapa output konsol kedua undefinedtetapi output konsol pertama event1?

Neuron
sumber
23
mengapa judulnya mengatakan "foreach" ketika pertanyaannya adalah tentang untuk ... dalam pernyataan? Saya datang ke sini secara tidak sengaja ketika googling.
mxt3
@ mxt3 Yah, dalam pemahaman saya, itu adalah anolog untuk setiap loop di Jawa (bekerja sama).
1
@ mxt3 Saya memikirkan hal yang sama! Tetapi setelah membaca jawaban yang diterima, saya menemukan baris ini, yang memecahkan masalah foreach saya, menggunakan Array.from ():Array.from(document.getElementsByClassName("events")).forEach(function(item) {
HoldOffHunger

Jawaban:

839

Menanggapi pertanyaan awal, Anda menggunakan for/insalah. Dalam kode Anda, keyadalah indeks. Jadi, untuk mendapatkan nilai dari pseudo-array, Anda harus melakukan list[key]dan mendapatkan id, Anda harus melakukannya list[key].id. Tapi, Anda seharusnya tidak melakukan hal ini for/insejak awal.

Ringkasan (ditambahkan pada Des 2018)

Jangan pernah gunakan for/inuntuk beralih ke nodeList atau HTMLCollection. Alasan untuk menghindarinya dijelaskan di bawah ini.

Semua versi terbaru dari browser modern (Safari, Firefox, Chrome, Edge) semua mendukung for/ofiterasi pada daftar DOM tersebut nodeListatau HTMLCollection.

Ini sebuah contoh:

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

Untuk menyertakan browser yang lebih lama (termasuk hal-hal seperti IE), ini akan berfungsi di mana saja:

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

Penjelasan Untuk Mengapa Anda Tidak Harus Menggunakan for/in

for/indimaksudkan untuk mengulangi properti dari suatu objek. Itu berarti ia akan mengembalikan semua properti yang dapat diubah dari suatu objek. Meskipun ini tampaknya berfungsi untuk sebuah array (mengembalikan elemen array atau elemen pseudo-array), itu juga dapat mengembalikan properti objek lain yang tidak seperti yang Anda harapkan dari elemen seperti array. Dan, coba tebak, suatu HTMLCollectionatau nodeListobjek keduanya bisa memiliki properti lain yang akan dikembalikan dengan for/initerasi. Saya baru saja mencoba ini di Chrome dan mengulanginya dengan cara Anda iterasi itu akan mengambil item dalam daftar (indeks 0, 1, 2, dll ...), tetapi juga akan mengambil lengthdan itemproperti. The for/initerasi hanya tidak akan bekerja untuk HTMLCollection.


Lihat http://jsfiddle.net/jfriend00/FzZ2H/ untuk alasan Anda tidak dapat mengulangi HTMLCollection dengan for/in.

Di Firefox, for/initerasi Anda akan mengembalikan item-item ini (semua properti objek yang dapat diulang):

0
1
2
item
namedItem
@@iterator
length

Mudah-mudahan, sekarang Anda dapat melihat mengapa Anda ingin menggunakannya for (var i = 0; i < list.length; i++)sebagai gantinya sehingga Anda dapatkan 0, 1dan 2dalam iterasi Anda.


Berikut di bawah ini adalah evolusi dari bagaimana browser telah berevolusi selama periode waktu 2015-2018 memberi Anda cara tambahan untuk beralih. Tidak satu pun dari ini sekarang diperlukan di browser modern karena Anda dapat menggunakan opsi yang dijelaskan di atas.

Pembaruan untuk ES6 pada tahun 2015

Ditambahkan ke ES6 Array.from()yang akan mengubah struktur mirip array ke array yang sebenarnya. Itu memungkinkan seseorang untuk menghitung daftar secara langsung seperti ini:

"use strict";

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

Demo yang berfungsi (di Firefox, Chrome, dan Edge pada April 2016): https://jsfiddle.net/jfriend00/8ar4xn2s/


Pembaruan untuk ES6 pada tahun 2016

Anda sekarang dapat menggunakan ES6 untuk / konstruksi dengan a NodeListdan HTMLCollectiondengan hanya menambahkan ini ke kode Anda:

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

Kemudian, Anda dapat melakukan:

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

Ini berfungsi di versi Chrome, Firefox, dan Edge saat ini. Ini berfungsi karena ia melampirkan iterator Array ke prototipe NodeList dan HTMLCollection sehingga ketika / untuk iterate mereka, ia menggunakan iterator Array untuk iterate mereka.

Demo kerja: http://jsfiddle.net/jfriend00/joy06u4e/ .


Pembaruan kedua untuk ES6 pada Desember 2016

Pada Desember 2016, Symbol.iteratordukungan telah terpasang di dalam Chrome v54 dan Firefox v50 sehingga kode di bawah ini bekerja dengan sendirinya. Itu belum built-in untuk Edge.

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

Demo yang berfungsi (di Chrome dan Firefox): http://jsfiddle.net/jfriend00/3ddpz8sp/

Pembaruan ketiga untuk ES6 pada bulan Desember 2017

Pada Desember 2017, kemampuan ini bekerja di Edge 41.16299.15.0 untuk a nodeListsebagai in document.querySelectorAll(), tetapi bukan HTMLCollectionsebagai in document.getElementsByClassName()sehingga Anda harus menetapkan secara manual iterator untuk menggunakannya di Edge untuk sebuah HTMLCollection. Ini adalah misteri total mengapa mereka memperbaiki satu jenis koleksi, tetapi tidak yang lain. Tapi, Anda setidaknya dapat menggunakan hasil document.querySelectorAll()dengan for/ofsintaks ES6 di versi Edge sekarang.

Saya juga memperbarui jsFiddle di atas sehingga ia menguji keduanya HTMLCollectiondan nodeListsecara terpisah dan menangkap output di jsFiddle itu sendiri.

Pembaruan Keempat untuk ES6 pada Maret 2018

Per mesqueeeb, Symbol.iteratordukungan telah built-in ke Safari juga, sehingga Anda dapat menggunakan for (let item of list)salah satu document.getElementsByClassName()atau document.querySelectorAll().

Pembaruan Kelima untuk ES6 pada bulan April 2018

Rupanya, dukungan untuk iterasi HTMLCollectiondengan for/ofakan datang ke Edge 18 pada musim gugur 2018.

Pembaruan Keenam untuk ES6 pada November 2018

Saya dapat mengonfirmasi bahwa dengan Microsoft Edge v18 (yang termasuk dalam Pembaruan Windows Musim Gugur 2018), Anda sekarang dapat melakukan iterasi HTMLCollection dan NodeList dengan for / of in Edge.

Jadi, sekarang semua browser modern berisi dukungan asli untuk for/ofiterasi dari objek HTMLCollection dan NodeList.

pacar00
sumber
1
Terima kasih atas pembaruan yang sangat terperinci karena JS telah memperbarui. Ini membantu pemula memahami saran ini dalam konteks artikel / posting lain yang hanya berlaku untuk rilis JS tertentu.
brownmagik352
Jawaban yang luar biasa, dukungan pembaruan yang luar biasa .. Terima kasih.
cossackman
80

Anda tidak dapat menggunakan for/ inpada NodeLists atau HTMLCollections. Namun, Anda dapat menggunakan beberapa Array.prototypemetode, selama Anda .call()mereka dan lulus dalam NodeListatau HTMLCollectionsebagai this.

Jadi pertimbangkan hal berikut sebagai alternatif untuk loop jfriend00for :

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

Ada artikel bagus tentang MDN yang membahas teknik ini. Perhatikan peringatan mereka tentang kompatibilitas browser:

[...] meneruskan objek host (seperti a NodeList) thiske metode asli (seperti forEach) tidak dijamin berfungsi di semua browser dan diketahui gagal di beberapa.

Jadi, meskipun pendekatan ini nyaman, satu forloop mungkin merupakan solusi yang paling kompatibel dengan browser.

Pembaruan (30 Agustus 2014): Akhirnya Anda akan dapat menggunakan ES6 for/of !

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

Itu sudah didukung di versi Chrome dan Firefox terbaru.

evanrmurphy
sumber
1
Sangat bagus! Saya menggunakan teknik ini untuk mendapatkan nilai opsi yang dipilih dari a <select multiple>. Contoh:[].map.call(multiSelect.selectedOptions, function(option) { return option.value; })
XåpplI'-I0llwlg'I -
1
Saya sedang mencari solusi ES2015 untuk ini, jadi terima kasih telah mengkonfirmasi itu for ... ofbekerja.
Richard Turner
60

Di ES6, Anda bisa melakukan sesuatu seperti [...collection], atau Array.from(collection),

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

Misalnya:-

    navDoms = document.getElementsByClassName('nav-container');
    Array.from(navDoms).forEach(function(navDom){
     //implement function operations
    });
mido
sumber
@DanielM tebak apa yang telah saya lakukan adalah, clone dangkal struktur seperti array
mido
Saya mengerti, terima kasih - sekarang saya telah menemukan dokumentasi yang saya cari: developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
DanielM
Saya selalu menggunakan ini, jauh lebih mudah bagi mata daripada Array. Dari, saya hanya ingin tahu apakah ini memiliki kinerja atau kekurangan memori yang cukup besar. Sebagai contoh jika saya perlu mengulangi sel baris tabel saya menggunakan [...row.cells].forEachalih - alih melakukanrow.querySelectorAll('td')
Mojimi
16

Anda dapat menambahkan dua baris ini:

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

HTMLCollection dikembalikan oleh getElementsByClassName dan getElementsByTagName

NodeList dikembalikan oleh querySelectorAll

Seperti ini, Anda dapat melakukan forEach:

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

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

selections.forEach(function(element, i){
//do your stuffs
});
mmnl
sumber
Jawaban ini sepertinya sangat efektif. Apa yang menangkap?
Peheje
2
Tangkapannya adalah bahwa solusi ini tidak berfungsi pada IE11! Solusi yang bagus.
Rahul Gaba
2
Perhatikan yang NodeListsudah adaforEach() .
Franklin Yu
7

Saya punya masalah menggunakan forEach di IE 11 dan juga Firefox 49

Saya telah menemukan solusi seperti ini

Array.prototype.slice.call(document.getElementsByClassName("events")).forEach(function (key) {
        console.log(key.id);
    }
Mamosek
sumber
Solusi hebat untuk IE11! Dulu teknik yang umum ...
mb21
6

Alternatif Array.fromadalah menggunakanArray.prototype.forEach.call

untuk setiap: Array.prototype.forEach.call(htmlCollection, i => { console.log(i) });

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

dll ...

holmberd
sumber
6

Tidak ada alasan untuk menggunakan fitur es6 untuk menghindari forperulangan jika Anda menggunakan IE9 atau lebih tinggi.

Dalam ES5, ada dua opsi bagus. Pertama, Anda bisa "meminjam" ArrayIni forEachsebagai evan menyebutkan .

Tetapi lebih baik lagi ...

Gunakan Object.keys(), yang memang memiliki forEachdan filter untuk "memiliki properti" secara otomatis.

Artinya, Object.keyspada dasarnya setara dengan melakukan for... indengan HasOwnProperty, tetapi jauh lebih halus.

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

Pada Maret 2016, di Chrome 49.0, for...ofberfungsi untuk HTMLCollection:

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

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

Lihat di sini dokumentasi .

Tapi itu hanya berfungsi jika Anda menerapkan solusi berikut sebelum menggunakan for...of:

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

Hal yang sama diperlukan untuk digunakan for...ofdengan NodeList:

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

Saya percaya / berharap for...ofakan segera bekerja tanpa solusi di atas. Masalah terbuka ada di sini:

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

Pembaruan: Lihat komentar Expenzor di bawah ini : Ini telah diperbaiki pada April 2016. Anda tidak perlu menambahkan HTMLCollection.prototype [Symbol.iterator] = Array.prototype [Symbol.iterator]; untuk beralih melalui HTMLCollection dengan untuk ... dari

MarcG
sumber
4
Ini telah diperbaiki pada April 2016. Anda tidak perlu menambahkan HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];untuk mengulangi HTMLCollectiondengan for...of.
Expenzor
3

Gelisah

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
sumber
2

Solusi mudah yang selalu saya gunakan

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

Setelah ini, Anda dapat menjalankan metode Array yang diinginkan pada seleksi

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

jika Anda menggunakan versi lama ES, (misalnya ES5), Anda dapat menggunakan as any:

for (let element of elementsToIterate as any) {
      console.log(element);
}
Alon Gouldman
sumber
0

Anda ingin mengubahnya menjadi

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
sumber
5
FYI, lihat jawaban saya mengapa ini tidak berhasil. The for (key in list)akan kembali beberapa properti dari HTMLCollectionyang tidak dimaksudkan untuk menjadi item dalam koleksi.
jfriend00