Mengapa nodelist tidak memiliki forEach?

91

Saya sedang mengerjakan skrip pendek untuk mengubah <abbr>teks bagian dalam elemen, tetapi ternyata nodelisttidak memiliki forEachmetode. Aku tahu itu nodelisttidak mewarisi Array, tapi bukankah itu forEachmetode yang berguna untuk dimiliki? Apakah ada masalah implementasi tertentu saya tidak sadar bahwa mencegah menambahkan forEachke nodelist?

Catatan: Saya tahu bahwa Dojo dan jQuery memiliki forEachbeberapa bentuk untuk nodelist mereka. Saya tidak dapat menggunakan keduanya karena keterbatasan.

Ular dan Kopi
sumber
6
Halo dari masa depan! nodeList memiliki forEach sejak ES6 .
Blaise

Jawaban:

94

NodeList sekarang memiliki forEach () di semua browser utama

Lihat nodeList forEach () di MDN .

Jawaban asli

Tak satu pun dari jawaban ini menjelaskan mengapa NodeList tidak mewarisi dari Array, sehingga memungkinkan NodeList untuk memiliki forEachdan yang lainnya.

Jawabannya ada di utas diskusi es ini . Singkatnya, ini merusak web:

Masalahnya adalah kode yang salah mengasumsikan instanceof yang berarti bahwa instance tersebut adalah Array dalam kombinasi dengan Array.prototype.concat.

Ada bug di Perpustakaan Penutupan Google yang menyebabkan hampir semua aplikasi Google gagal karena ini. Pustaka diperbarui segera setelah ditemukan, tetapi mungkin masih ada kode di luar sana yang membuat asumsi salah yang sama dikombinasikan dengan concat.

Artinya, beberapa kode melakukan sesuatu seperti

if (x instanceof Array) {
  otherArray.concat(x);
} else {
  doSomethingElseWith(x);
}

Namun, concatakan memperlakukan larik "nyata" (bukan turunan dari Array) secara berbeda dari objek lain:

[1, 2, 3].concat([4, 5, 6]) // [1, 2, 3, 4, 5, 6]
[1, 2, 3].concat(4) // [1, 2, 3, 4]

jadi itu berarti bahwa kode di atas rusak ketika xmenjadi NodeList, karena sebelum itu turun ke doSomethingElseWith(x)jalur, sedangkan setelah itu turun ke otherArray.concat(x)jalur, yang melakukan sesuatu yang aneh karena xbukan array nyata.

Untuk beberapa waktu ada proposal untuk Elementskelas yang merupakan subkelas Array yang sebenarnya, dan akan digunakan sebagai "NodeList baru". Namun, itu telah dihapus dari Standar DOM , setidaknya untuk saat ini, karena belum dapat diterapkan karena berbagai alasan teknis dan spesifikasi.

Domenik
sumber
11
Sepertinya panggilan yang buruk bagiku. Serius, saya pikir itu keputusan yang tepat untuk memecahkan barang sesekali, terutama jika itu berarti kita memiliki API yang waras untuk masa depan. Selain itu, tidak seperti web yang hampir menjadi platform yang stabil, orang-orang yang terbiasa dengan javascript berusia 2 tahun mereka tidak lagi berfungsi seperti yang diharapkan ..
JoyalToTheWorld
3
"Breaks the web"! = "Breaks the Google stuff"
Matt
Mengapa tidak meminjam saja metode forEach dari Array.prototype? Misalnya, alih-alih menambahkan Array sebagai prototipe, lakukan saja this.forEach = Array.prototype.forEach di konstruktor, atau bahkan hanya mengimplementasikan forEach secara unik untuk NodeList?
PopKernel
1
Catatan; pembaruan ini NodeList now has forEach() in all major browserssepertinya menyiratkan bahwa IE bukanlah browser utama. Mudah-mudahan itu benar bagi sebagian orang, tapi itu tidak benar bagi saya (belum).
Graham P Heath
58

Anda dapat melakukan

Array.prototype.forEach.call (nodeList, function (node) {

    // Your code here.

} );
akuhn
sumber
3
Array.prototype.forEach.calldapat disingkat menjadi[].forEach.call
CodeBrauer
7
@CodeBrauer: itu tidak hanya memperpendek Array.prototype.forEach.call, itu membuat array kosong dan menggunakan forEachmetodenya.
Paul D. Waite
34

Anda dapat mempertimbangkan untuk membuat array node baru.

  var nodeList = document.getElementsByTagName('div'),

      nodes = Array.prototype.slice.call(nodeList,0); 

  // nodes is an array now.
  nodes.forEach(function(node){ 

       // do your stuff here.  

  });

Catatan: Ini hanyalah daftar / larik referensi node yang kami buat di sini, tidak ada node duplikat.

  nodes[0] === nodeList[0] // will be true
sbr
sumber
22
Atau lakukan saja Array.prototype.forEach.call(nodeList, fun).
akuhn
Saya juga akan menyarankan aliasing fungsi forEach sehingga: var forEach = Array.prototype.forEach.call(nodeList, callback);. Sekarang Anda cukup meneleponforEach(nodeList, callback);
Andrew Craswell
19

Jangan pernah katakan tidak, ini tahun 2016 dan NodeListobjek telah menerapkan forEachmetode di chrome terbaru (v52.0.2743.116).

Masih terlalu dini untuk menggunakannya dalam produksi karena browser lain belum mendukung ini (diuji FF 49) tetapi saya kira ini akan segera distandarisasi.

maioman
sumber
2
Opera juga mendukungnya dan dukungan akan ditambahkan pada Firefox v50, dijadwalkan rilis pada 15/11/16.
Shaggy
1
Meskipun diterapkan, itu bukan bagian dari standar apa pun. Yang terbaik Array.prototype.slice.call(nodelist).forEach(…)adalah melakukan yang standar dan berfungsi di browser lama.
Nate
17

Singkatnya, itu adalah konflik desain untuk mengimplementasikan metode itu.

Dari MDN:

Mengapa saya tidak dapat menggunakan forEach atau map pada NodeList?

NodeList digunakan sangat mirip dengan array dan akan tergoda untuk menggunakan metode Array.prototype pada mereka. Namun, ini tidak mungkin.

JavaScript memiliki mekanisme pewarisan berdasarkan prototipe. Instance array mewarisi metode array (seperti forEach atau map) karena rantai prototipe mereka terlihat seperti berikut:

myArray --> Array.prototype --> Object.prototype --> null (rantai prototipe suatu objek dapat diperoleh dengan memanggil beberapa kali Object.getPrototypeOf)

forEach, map dan sejenisnya adalah properti sendiri dari objek Array.prototype.

Tidak seperti array, rantai prototipe NodeList terlihat seperti berikut:

myNodeList --> NodeList.prototype --> Object.prototype --> null

NodeList.prototype berisi metode item, tetapi tidak ada metode Array.prototype, sehingga tidak dapat digunakan di NodeLists.

Sumber: https://developer.mozilla.org/en-US/docs/DOM/NodeList (gulir ke bawah ke Why can't I use forEach atau map on a NodeList? )

Matt Lo
sumber
8
Jadi, karena ini daftar, mengapa didesain seperti itu? Apa yang menghentikan mereka untuk rantai make: myNodeList --> NodeList.prototype --> Array.prototype --> Object.prototype --> null?
Igor Pantović
14

Jika Anda ingin menggunakan forEach di NodeList, cukup salin fungsi itu dari Array:

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

Itu saja, sekarang Anda dapat menggunakannya dengan cara yang sama seperti yang Anda lakukan untuk Array:

document.querySelectorAll('td').forEach(function(o){
   o.innerHTML = 'text';
});
AlexTR
sumber
4
Kecuali bahwa mutasi kelas dasar tidak terlalu eksplisit bagi pembaca rata-rata. Dengan kata lain, jauh di dalam beberapa kode, Anda harus mengingat setiap penyesuaian yang Anda buat pada objek dasar browser. Mengandalkan dokumentasi MDN tidak lagi berguna karena objek telah mengubah perilaku dari norma. Lebih baik menerapkan prototipe secara eksplisit pada waktu panggilan sehingga pembaca dapat dengan mudah menyadari bahwa forEach adalah ide pinjaman dan bukan sesuatu yang merupakan bagian dari definisi bahasa. Lihat jawaban @akuhn di atas.
Sukima
@Sukima salah kaprah dengan premis yang salah. Dalam kasus khusus ini, pendekatan yang diberikan memperbaiki NodeList agar berperilaku seperti yang diharapkan oleh pengembang. Ini adalah cara paling tepat untuk memperbaiki masalah Kelas sistem. (NodeList tampaknya belum selesai dan harus diperbaiki dalam versi bahasa yang akan datang.)
Daniel Garmoshka
1
Sekarang setelah NodeList.forEach ada, ini menjadi polyfill yang sangat sederhana!
Damon
7

Di ES2015, Anda sekarang dapat menggunakan forEachmetode ke nodeList.

document.querySelectorAll('abbr').forEach( el => console.log(el));

Lihat Tautan MDN

Namun jika Anda ingin menggunakan Koleksi HTML atau objek lain yang mirip array, di es2015, Anda dapat menggunakan Array.from()metode. Metode ini mengambil objek seperti array atau iterable (termasuk nodeList, Koleksi HTML, string, dll.) Dan mengembalikan instance Array baru. Anda bisa menggunakannya seperti ini:

const elements = document.getElementsByTagName('abbr');
Array.from(elements).forEach( el => console.log(el));

Karena Array.from()metode ini dapat diubah, Anda dapat menggunakannya dalam kode es5 seperti ini

var elements = document.getElementsByTagName('abbr');
Array.from(elements).forEach( function(el) {
    console.log(el);
});

Untuk detailnya, lihat halaman MDN .

Untuk memeriksa dukungan browser saat ini .

ATAU

cara es2015 lainnya adalah dengan menggunakan operator penyebaran.

[...document.querySelectorAll('abbr')].forEach( el => console.log(el));

Operator penyebaran MDN

Spread Operator - Dukungan Browser

Mishel Tanvir Habib
sumber
2

Solusi saya:

//foreach for nodeList
NodeList.prototype.forEach = Array.prototype.forEach;
//foreach for HTML collection(getElementsByClassName etc.)
HTMLCollection.prototype.forEach = Array.prototype.forEach;
Bakos Bence
sumber
1
Seringkali bukan ide yang baik untuk memperluas fungsionalitas DOM melalui prototipe, terutama di versi IE ( artikel ) yang lebih lama.
KFE
0

NodeList adalah bagian dari DOM API. Lihat binding ECMAScript yang juga berlaku untuk JavaScript. http://www.w3.org/TR/DOM-Level-2-Core/ecma-script-binding.html . NodeList dan properti panjang baca-saja dan item (indeks) berfungsi untuk mengembalikan sebuah node.

Jawabannya adalah, Anda harus mengulang. Tidak ada alternatif lain. Foreach tidak akan berhasil. Saya bekerja dengan binding Java DOM API dan memiliki masalah yang sama.

randominstanceOfLivingThing
sumber
Tetapi adakah alasan khusus mengapa itu tidak boleh diterapkan? Baik jQuery dan Dojo telah menerapkannya di perpustakaan mereka sendiri
Snakes and Coffee
2
tapi bagaimana itu bisa menjadi konflik desain?
Ular dan Kopi
0

Periksa MDN untuk spesifikasi NodeList.forEach .

NodeList.forEach(function(item, index, nodeList) {
    // code block here
});

Di IE, gunakan jawaban akuhn :

[].forEach.call(NodeList, function(item, index, array) {
    // code block here
});
VesperX
sumber