JS: mengulangi hasil getElementsByClassName menggunakan Array.forEach

240

Saya ingin mengulangi beberapa elemen DOM, saya melakukan ini:

document.getElementsByClassName( "myclass" ).forEach( function(element, index, array) {
  //do stuff
});

tapi saya mendapatkan kesalahan:

document.getElementsByClassName ("myclass"). forEach bukan fungsi

Saya menggunakan Firefox 3 jadi saya tahu keduanya getElementsByClassNamedan Array.forEachada. Ini berfungsi dengan baik:

[2, 5, 9].forEach( function(element, index, array) {
  //do stuff
});

Apakah hasil dari getElementsByClassNameArray? Jika tidak, apa itu?

Steve Claridge
sumber

Jawaban:

384

Tidak. Seperti yang ditentukan dalam DOM4 , ini adalah HTMLCollection(di browser modern, setidaknya. Browser yang lebih lama mengembalikan a NodeList).

Di semua browser modern (hampir semua IE lainnya <= 8), Anda dapat memanggil forEachmetode Array , meneruskannya daftar elemen (baik itu HTMLCollectionatau NodeList) sebagai thisnilainya:

var els = document.getElementsByClassName("myclass");

Array.prototype.forEach.call(els, function(el) {
    // Do stuff here
    console.log(el.tagName);
});

// Or
[].forEach.call(els, function (el) {...});

Jika Anda berada dalam posisi senang dapat menggunakan ES6 (yaitu Anda dapat dengan aman mengabaikan Internet Explorer atau Anda menggunakan transpiler ES5), Anda dapat menggunakan Array.from:

Array.from(els).forEach((el) => {
    // Do stuff here
    console.log(el.tagName);
});
Tim Down
sumber
29
Tidak perlu mengubahnya menjadi Array terlebih dahulu. Gunakan saja [].forEach.call(elsArray, function () {...}).
kay - SE jahat
1
Ini BUKAN NodeList. Ini adalah objek seperti array. Saya bahkan tidak berpikir itu memiliki tipe instance. querySelectorAllMetode mengembalikan NodeList sekalipun.
Maksim Vi.
2
@MaksimVi. Anda sepenuhnya benar: DOM4 menentukan bahwa document.getElementsByClassName()harus mengembalikan HTMLCollection(yang sangat mirip tetapi bukan NodeList). Terima kasih telah menunjukkan kesalahannya.
Tim Down
@MaksimVi .: Saya ingin tahu apakah itu berubah di beberapa titik. Saya biasanya memeriksa hal-hal ini.
Tim Down
1
@TimDown, Terima kasih atas HTMLCollectiontipnya. Sekarang saya akhirnya dapat menggunakan HTMLCollection.prototype.forEach = Array.prototype.forEach;kode saya.
Maksim Vi.
70

Anda dapat menggunakan Array.fromuntuk mengonversi koleksi menjadi array, yang jauh lebih bersih daripada Array.prototype.forEach.call:

Array.from(document.getElementsByClassName("myclass")).forEach(
    function(element, index, array) {
        // do stuff
    }
);

Di browser lama yang tidak mendukung Array.from, Anda perlu menggunakan sesuatu seperti Babel.


ES6 juga menambahkan sintaks ini:

[...document.getElementsByClassName("myclass")].forEach(
    (element, index, array) => {
        // do stuff
    }
);

Rest Destrukturisasi dengan ...bekerja pada semua objek seperti array, tidak hanya array sendiri, maka sintaks array lama yang baik digunakan untuk membangun sebuah array dari nilai-nilai.


Sementara fungsi alternatif querySelectorAll(yang agaknya getElementsByClassNameusang) mengembalikan koleksi yang memiliki forEachsecara native, metode lain suka mapatau filtertidak ada, jadi sintaks ini masih berguna:

[...document.querySelectorAll(".myclass")].map(
    (element, index, array) => {
        // do stuff
    }
);

[...document.querySelectorAll(".myclass")].map(element => element.innerHTML);
Athari
sumber
6
Catatan: tanpa mengubah seperti yang disarankan (Babel), ini TIDAK kompatibel di IE <Edge, Opera, Safari <9, browser Android, Chrome untuk Android, ... dll) Sumber: mozilla dev docs
Sean
30

Atau Anda dapat menggunakan querySelectorAllyang mengembalikan NodeList :

document.querySelectorAll('.myclass').forEach(...)

Didukung oleh browser modern (termasuk Edge, tetapi bukan IE):
Dapatkah saya menggunakan querySelectorAll
NodeList.prototype.forEach ()

MDN: Document.querySelectorAll ()

icl7126
sumber
4
Ingatlah penalti kinerja atas getElementByClassName
Szabolcs Páll
3
Denda kinerja dapat diabaikan dibandingkan dengan tugas lain yang lebih intensif seperti memodifikasi DOM. Jika saya menjalankan 60.000 dari ini dalam 1 milidetik , saya cukup yakin itu tidak akan menjadi masalah untuk penggunaan yang wajar :)
icl7126
1
Anda menautkan tolok ukur yang salah. Ini adalah yang benar Measethat.net/Benchmarks/Show/4076/0/... Cukup jalankan di ponsel low-end saya, dapatkan 160k / s vs 380k / s. Karena Anda menyebutkan manipulasi DOM, di sini adalah itu juga ukurethat.net/Benchmarks/Show/5705/0/… Mendapat 50k / s vs 130k / s. Seperti yang Anda lihat, bahkan lebih lambat untuk memanipulasi DOM, kemungkinan karena NodeList bersifat statis (seperti yang disebutkan oleh orang lain). Masih dapat diperdebatkan dalam kebanyakan kasus penggunaan, namun hampir 3 kali lipat lebih lambat.
Szabolcs Páll
14

Sunting: Meskipun jenis kembali telah berubah dalam versi HTML baru (lihat jawaban Tim Down yang diperbarui), kode di bawah ini masih berfungsi.

Seperti yang orang lain katakan, ini adalah NodeList. Berikut adalah contoh yang lengkap dan berfungsi yang dapat Anda coba:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <script>
            function findTheOddOnes()
            {
                var theOddOnes = document.getElementsByClassName("odd");
                for(var i=0; i<theOddOnes.length; i++)
                {
                    alert(theOddOnes[i].innerHTML);
                }
            }
        </script>
    </head>
    <body>
        <h1>getElementsByClassName Test</h1>
        <p class="odd">This is an odd para.</p>
        <p>This is an even para.</p>
        <p class="odd">This one is also odd.</p>
        <p>This one is not odd.</p>
        <form>
            <input type="button" value="Find the odd ones..." onclick="findTheOddOnes()">
        </form>
    </body>
</html>

Ini berfungsi di IE 9, FF 5, Safari 5, dan Chrome 12 pada Win 7.

james.garriss
sumber
9

Hasil getElementsByClassName()bukan Array, tetapi objek seperti array . Khususnya itu disebut HTMLCollection, jangan dikacaukan dengan NodeList( yang memiliki forEach()metode sendiri ).

Salah satu cara sederhana dengan ES2015 untuk mengonversi objek mirip array untuk digunakan dengan Array.prototype.forEach()yang belum disebutkan adalah dengan menggunakan operator spread atau menyebar sintaksis :

const elementsArray = document.getElementsByClassName('myclass');

[...elementsArray].forEach((element, index, array) => {
    // do something
});
Kloptikus
sumber
2
Saya merasa ini benar-benar cara yang tepat untuk melakukannya di browser modern. Ini adalah sintaksis penyebaran kasus penggunaan yang tepat yang dibuat untuk dipecahkan.
Matt Korostoff
3

Seperti yang sudah dikatakan, getElementsByClassNamemengembalikan HTMLCollection , yang didefinisikan sebagai

[Exposed=Window]
interface HTMLCollection {
  readonly attribute unsigned long length;
  getter Element? item(unsigned long index);
  getter Element? namedItem(DOMString name);
};

Sebelumnya, beberapa browser malah mengembalikan NodeList .

[Exposed=Window]
interface NodeList {
  getter Node? item(unsigned long index);
  readonly attribute unsigned long length;
  iterable<Node>;
};

Perbedaannya penting, karena DOM4 sekarang mendefinisikan NodeList sebagai yang dapat diubah.

Menurut konsep Web IDL ,

Objek yang mengimplementasikan antarmuka yang dinyatakan sebagai dukungan yang dapat diulangi untuk mendapatkan urutan nilai.

Catatan : Dalam pengikatan bahasa ECMAScript, sebuah antarmuka yang dapat diubah akan memiliki "entri", "forEach", "kunci", "nilai" dan properti @@ iterator pada objek prototipe antarmuka .

Itu berarti, jika Anda ingin menggunakan forEach, Anda dapat menggunakan metode DOM yang mengembalikan NodeList , seperti querySelectorAll.

document.querySelectorAll(".myclass").forEach(function(element, index, array) {
  // do stuff
});

Catatan ini belum didukung secara luas. Lihat juga metode forEach dari Node.childNodes?

Oriol
sumber
1
forEach in not a function
Pengembalian
@VitalyZdanevich Coba Chromium 50
Oriol
Di Chrome 50 saya mendapatkandocument.querySelectorAll(...).forEach is not a function
Vitaly Zdanevich
@VitalyZdanevich Ini berfungsi pada Chromium 50, dan masih bekerja pada Chromium 53. Mungkin itu tidak dianggap cukup stabil untuk dikirim ke Chrome 50.
Oriol
1

Itu tidak mengembalikan Array, mengembalikan NodeList .

reko_t
sumber
1

Ini cara yang lebih aman:

var elements = document.getElementsByClassName("myclass");
for (var i = 0; i < elements.length; i++) myFunction(elements[i]);
gildniy
sumber
0

getElementsByClassNamemengembalikan HTMLCollection di browser modern.

yang merupakan objek mirip array dengan argumen yang dapat diulang dengan for...ofloop, lihat di bawah apa yang dikatakan MDN doc tentangnya:

The for ... dari statement menciptakan sebuah loop iterasi pada objek iterable , termasuk: built-in String, Array, objek seperti array (misalnya, argumen atau NodeList), TypedArray, Map, Set, dan iterables yang ditentukan pengguna. Itu memanggil kait iterasi kustom dengan pernyataan yang akan dieksekusi untuk nilai setiap properti objek yang berbeda.

contoh

for (let element of getElementsByClassName("classname")){
   element.style.display="none";
}
Haritsinh Gohil
sumber
Tidak demikian, menurut Typescript:error TS2488: Type 'HTMLCollectionOf<Element>' must have a '[Symbol.iterator]()' method that returns an iterator.
Turtles Are Cute
@TurtlesAreCute, Di sini OP menggunakan javascript bukan naskah dan saya telah menjawab sesuai dengan rekomendasi vanilla js sehingga dalam naskah itu bisa menjadi solusi yang berbeda untuk masalah ini.
Haritsinh Gohil
@TurtlesAreCute, Ngomong-ngomong itu juga bekerja dalam naskah juga, tetapi Anda harus menyebutkan jenis variabel yang tepat yang memegang elemen kelas css tertentu, sehingga dapat melemparkannya sesuai, untuk detail lihat jawaban ini .
Haritsinh Gohil
0

Ini adalah tes yang saya buat di jsperf: https://jsperf.com/vanillajs-loop-through-elements-of-class

Versi yang paling bagus di Chrome dan Firefox adalah versi lama yang bagus untuk loop dalam kombinasi dengan document.getElementsByClassName:

var elements = document.getElementsByClassName('testClass'), elLength = elements.length;
for (var i = 0; i < elLength; i++) {
    elements.item(i).textContent = 'Tested';
};

Di Safari, varian ini adalah pemenangnya:

var elements = document.querySelectorAll('.testClass');
elements.forEach((element) => {
    element.textContent = 'Tested';
});

Jika Anda ingin varian yang paling bagus untuk semua browser, mungkin yang ini:

var elements = document.getElementsByClassName('testClass');
Array.from(elements).map(
    (element) => {
        return element.textContent = 'Tested';
    }
);
StefanSL
sumber