Mengapa document.querySelectorAll mengembalikan StaticNodeList daripada Array yang sebenarnya?

103

Itu mengganggu saya yang tidak bisa saya lakukan document.querySelectorAll(...).map(...)bahkan di Firefox 3.6, dan saya masih tidak dapat menemukan jawaban, jadi saya pikir saya akan posting silang di SO pertanyaan dari blog ini:

http://blowery.org/2008/08/29/yay-for-queryselectorall-boo-for-staticnodelist/

Adakah yang tahu tentang alasan teknis mengapa Anda tidak mendapatkan Array? Atau mengapa StaticNodeList tidak mewarisi dari Array dengan cara seperti yang dapat Anda gunakan map, concat, etc?

(BTW jika itu hanya satu fungsi yang Anda inginkan, Anda dapat melakukan sesuatu seperti NodeList.prototype.map = Array.prototype.map;... tetapi sekali lagi, mengapa fungsi ini (sengaja?) Diblokir sejak awal?)

Kev
sumber
3
Sebenarnya juga getElementsByTagName tidak mengembalikan Array, tetapi koleksi, dan jika Anda ingin menggunakannya seperti Array (dengan metode seperti concat, dll.) Anda harus mengubah koleksi tersebut menjadi Array dengan melakukan loop dan menyalin setiap elemen koleksi ke dalam Array. Tidak ada yang pernah mengeluh tentang ini.
Marco Demaio

Jawaban:

81

Saya yakin ini adalah keputusan filosofis W3C. Desain [spesifikasi] W3C DOM cukup ortogonal dengan desain JavaScript, karena DOM dimaksudkan untuk menjadi platform dan bahasa yang netral.

Keputusan seperti " getElementsByFoo()mengembalikan urutan NodeList" atau " querySelectorAll()mengembalikan StaticNodeList" sangat disengaja, sehingga implementasi tidak perlu khawatir tentang menyelaraskan struktur data yang dikembalikan berdasarkan implementasi yang bergantung pada bahasa (seperti .maptersedia di Array di JavaScript dan Ruby, tetapi tidak ada dalam Daftar di C #).

W3C Tujuan rendah: mereka akan mengatakan NodeListharus berisi readonly .lengthproperti jenis unsigned panjang karena mereka percaya setiap pelaksanaan kaleng setidaknya dukungan yang , tetapi mereka tidak akan mengatakan secara eksplisit bahwa []Operator indeks harus kelebihan beban untuk mendukung mendapatkan elemen posisi, karena mereka tidak ingin menghalangi beberapa bahasa kecil yang buruk yang ingin diterapkan getElementsByFoo()tetapi tidak dapat mendukung kelebihan beban operator. Ini adalah filosofi umum yang ada di sebagian besar spesifikasi.

John Resig telah menyuarakan opsi yang sama seperti milik Anda, yang dia tambahkan :

Argumen saya tidak begitu banyak yang NodeIteratortidak terlalu mirip DOM, tetapi tidak terlalu mirip JavaScript. Itu tidak memanfaatkan fitur yang ada dalam bahasa JavaScript dan menggunakannya dengan kemampuan terbaiknya ...

Saya agak berempati. Jika DOM ditulis secara khusus dengan mempertimbangkan fitur JavaScript, itu akan menjadi jauh lebih tidak canggung dan lebih intuitif untuk digunakan. Pada saat yang sama, saya memahami keputusan desain W3C.

Crescent Fresh
sumber
Terima kasih, itu membantu saya memahami situasinya.
Kev
@Kev: Saya melihat komentar Anda di halaman artikel blog yang mempertanyakan bagaimana Anda akan mengubah StaticNodeListke sebuah array. Saya akan mendukung jawaban @ mck89 sebagai cara untuk mengubah a NodeList/ StaticNodeListke Array asli, tetapi itu akan gagal di IE (8 obv) dengan kesalahan JScript, karena objek tersebut di-host / "khusus".
Crescent Fresh
Benar, itulah mengapa saya memuji dia. Namun, ada orang lain yang membatalkan +1 saya. Apa yang Anda maksud dengan host / spesial?
Kev
1
@Kev: variabel yang dihosting adalah variabel apa pun yang disediakan oleh lingkungan "host" (misalnya, browser web). Misalnya document, windowdll. IE sering mengimplementasikan "khusus" ini (misalnya sebagai objek COM) yang terkadang tidak sesuai dengan penggunaan normal, dengan cara yang kecil dan halus, seperti Array.prototype.slice.callmembom ketika diberi StaticNodeList;)
Crescent Fresh
201

Anda dapat menggunakan operator penyebaran ES2015 (ES6) :

[...document.querySelectorAll('div')]

akan mengubah StaticNodeList menjadi Array of items.

Berikut ini contoh cara menggunakannya.

[...document.querySelectorAll('div')].map(x => console.log(x.innerHTML))
<div>Text 1</div>
<div>Text 2</div>

Vlad Bezden
sumber
24
Cara lain adalah dengan menggunakan Array.from () :Array.from(document.querySelectorAll('div')).map(x => console.log(x.innerHTML))
Michael Berdyshev
42

Saya tidak tahu mengapa ini mengembalikan daftar node daripada array, mungkin karena seperti getElementsByTagName itu akan memperbarui hasil ketika Anda memperbarui DOM. Bagaimanapun, metode yang sangat sederhana untuk mengubah hasil itu menjadi array sederhana adalah:

Array.prototype.slice.call(document.querySelectorAll(...));

dan kemudian Anda dapat melakukan:

Array.prototype.slice.call(document.querySelectorAll(...)).map(...);
mck89
sumber
3
Sebenarnya itu tidak memperbarui hasil ketika Anda memperbarui DOM - karenanya 'statis'. Anda harus memanggil qSA lagi secara manual untuk memperbarui hasilnya. 1 untuk slicegaris sekalipun.
Kev
1
Ya, seperti yang Kev katakan: kumpulan hasil qSA statis, kumpulan hasil getElementsByTagName () dinamis.
joonas.fi
IE8 hanya mendukung querySelectorAll () dalam mode standar
mbokil
13

Hanya untuk menambah apa yang dikatakan Crescent,

jika hanya satu fungsi yang Anda inginkan, Anda dapat melakukan sesuatu seperti NodeList.prototype.map = Array.prototype.map

Jangan lakukan ini! Sama sekali tidak dijamin berhasil.

Tidak ada standar JavaScript atau DOM / BOM yang menentukan bahwa fungsi NodeListkonstruktor bahkan ada sebagai global / windowproperti, atau yang NodeListdikembalikan oleh querySelectorAllakan mewarisi darinya, atau bahwa prototipe-nya dapat ditulis, atau bahwa fungsi Array.prototype.maptersebut benar-benar akan berfungsi pada NodeList.

NodeList diizinkan untuk menjadi 'objek host' (dan merupakan salah satunya, di IE dan beberapa browser lama). The Arraymetode didefinisikan sebagai diizinkan untuk beroperasi pada 'objek asli' setiap JavaScript yang mengekspos numerik dan lengthproperti, tapi mereka tidak diharuskan untuk bekerja pada objek host (dan di IE, mereka tidak).

Sangat menjengkelkan karena Anda tidak mendapatkan semua metode array pada daftar DOM (semuanya, bukan hanya StaticNodeList), tetapi tidak ada cara yang dapat diandalkan untuk memutarnya. Anda harus mengonversi setiap daftar DOM yang Anda dapatkan kembali ke Array secara manual:

Array.fromList= function(list) {
    var array= new Array(list.length);
    for (var i= 0, n= list.length; i<n; i++)
        array[i]= list[i];
    return array;
};

Array.fromList(element.childNodes).forEach(function() {
    ...
});
bobince
sumber
1
Sial, aku tidak memikirkan itu. Terima kasih!
Kev
Saya setuju +1. Hanya komentar, saya pikir melakukan "var array = []" daripada "var array = new Array (list.length)" akan membuat kode lebih pendek. Tapi saya tertarik jika Anda tahu mungkin ada masalah dalam melakukan ini.
Marco Demaio
@MarcoDemaio: Tidak, tidak masalah. new Array(n)hanya memberi JS terp petunjuk tentang berapa lama array akan berakhir. Itu bisa memungkinkannya untuk mengalokasikan jumlah ruang itu sebelumnya, yang berpotensi menghasilkan percepatan karena beberapa realokasi memori dapat dihindari saat array bertambah. Saya tidak tahu apakah itu benar-benar membantu di peramban modern ... Saya kira tidak dapat diukur.
bobince
2
Sekarang diterapkan di Array.from ()
Michael Berdyshev
2

Saya pikir Anda cukup mengikuti

Array.prototype.map.call(document.querySelectorAll(...), function(...){...});

Ini bekerja sempurna untuk saya

Max Leps
sumber
0

Ini adalah opsi yang ingin saya tambahkan ke berbagai kemungkinan lain yang disarankan oleh orang lain di sini. Ini dimaksudkan untuk kesenangan intelektual saja dan tidak disarankan .


Hanya untuk iseng , berikut cara untuk "memaksa" querySelectorAllberlutut dan membungkuk kepada Anda:

Element.prototype.querySelectorAll = (function(QSA){
    return function(){
        return [...QSA.call(this, arguments[0])]
    }
})(Element.prototype.querySelectorAll);

Sekarang rasanya menyenangkan untuk melangkah ke seluruh fungsi itu, menunjukkan siapa bosnya. Sekarang saya tidak tahu apa yang lebih baik, membuat pembungkus fungsi bernama baru dan kemudian meminta semua kode Anda menggunakan nama aneh itu (cukup banyak gaya jQuery) atau menimpa fungsi seperti di atas sekali sehingga sisa kode Anda masih bisa untuk menggunakan nama metode DOM asli querySelectorAll.

  • Pendekatan seperti itu akan menghilangkan kemungkinan penggunaan sub-metode

Saya tidak akan merekomendasikan ini dengan cara apa pun, kecuali jika Anda benar-benar tidak memberi [Anda tahu apa].

vsync
sumber