Bagaimana cara mengubah daftar simpul DOM menjadi array di Javascript?

96

Saya memiliki fungsi Javascript yang menerima daftar node HTML, tetapi mengharapkan array Javascript (menjalankan beberapa metode Array di atasnya) dan saya ingin memberi makan output Document.getElementsByTagNameyang mengembalikan daftar node DOM.

Awalnya saya berpikir untuk menggunakan sesuatu yang sederhana seperti:

Array.prototype.slice.call(list,0)

Dan itu berfungsi dengan baik di semua browser, kecuali tentu saja Internet Explorer yang mengembalikan kesalahan "objek JScript diharapkan", karena tampaknya daftar simpul DOM yang dikembalikan oleh Document.getElement*metode bukan objek JScript yang cukup untuk menjadi target pemanggilan fungsi.

Peringatan: Saya tidak keberatan menulis kode khusus Internet Explorer, tetapi saya tidak diizinkan menggunakan pustaka Javascript apa pun seperti JQuery karena saya menulis widget untuk disematkan ke situs web pihak ketiga, dan saya tidak dapat memuat pustaka eksternal yang akan menciptakan konflik untuk klien.

Upaya terakhir saya adalah mengulang daftar simpul DOM dan membuat array sendiri, tetapi adakah cara yang lebih baik untuk melakukannya?

Guss
sumber
Lebih baik lagi, buat fungsi untuk mengonversi dari daftar simpul DOM, tetapi itu benar-benar akan menjadi solusi saya, saya pikir Anda sudah benar.
Kristoffer Sall-Storgaard
> for (i = 0; i & lt; x.length; i ++) Mengapa mendapatkan panjang NodeList di setiap iterasi? Ini tidak hanya membuang-buang waktu, tetapi karena NodeLists adalah koleksi langsung, jika ada sesuatu di badan loop yang mengubah panjangnya, Anda dapat melakukan loop tanpa henti atau mencapai indeks di luar batas. Yang terakhir adalah yang terburuk yang bisa terjadi jika Anda menetapkan panjang ke variabel, dan kesalahan jauh lebih baik daripada loop tanpa akhir.
Ini adalah pertanyaan yang sangat lama, tetapi jQuery dibuat dengan metode .noConflict secara khusus sehingga tidak akan menyebabkan konflik dengan pustaka lain (bahkan dirinya sendiri), yang berarti bahwa beberapa versi jQuery dapat dimuat di halaman. Karena itu, sebaiknya hindari menggunakan / memuat perpustakaan kecuali Anda benar-benar harus melakukannya.
vol7ron
@ vol7ron: maju cepat ke 2016, dan semua orang masih khawatir tentang ukuran yang ditambahkan perpustakaan javascript ke halaman. Memang, JQuery yang diperkecil dan di-gzip adalah 30KB, itu masih 30KB terlalu banyak hanya untuk mengubah daftar node :-)
Guss

Jawaban:

64

NodeLists adalah objek host , menggunakan Array.prototype.slicemetode pada objek host tidak dijamin akan berfungsi, Spesifikasi ECMAScript menyatakan:

Apakah fungsi slice dapat berhasil diterapkan ke objek host bergantung pada implementasi.

Saya akan merekomendasikan Anda untuk membuat fungsi sederhana untuk mengulang NodeListdan menambahkan setiap elemen yang ada ke array:

function toArray(obj) {
  var array = [];
  // iterate backwards ensuring that length is an UInt32
  for (var i = obj.length >>> 0; i--;) { 
    array[i] = obj[i];
  }
  return array;
}

MEMPERBARUI:

Seperti yang disarankan jawaban lain, Anda sekarang dapat menggunakan di lingkungan modern sintaks penyebaran atau Array.frommetode:

const array = [ ...nodeList ] // or Array.from(nodeList)

Tetapi memikirkannya, saya kira kasus penggunaan yang paling umum untuk mengonversi NodeList menjadi Array adalah mengulanginya, dan sekarang NodeList.prototypeobjek tersebut memiliki forEachmetode aslinya , jadi jika Anda berada di lingkungan modern Anda dapat menggunakannya secara langsung, atau memiliki sebuah pollyfill.

Christian C. Salvadó
sumber
2
Ini membuat array dengan urutan asli daftar dibalik, yang menurut saya bukan yang diinginkan OP. Apakah Anda bermaksud melakukan, array[i] = obj[i]bukan array.push(obj[i])?
Tim Down
@Tim, benar, saya sudah seperti itu sebelumnya tetapi diedit kemarin malam tanpa menyadarinya (3AM waktu setempat :), Terima kasih !.
Christian C. Salvadó
9
Dalam keadaan obj.lengthapa ada yang lain selain nilai integer?
Peter
1
Saya tidak percaya itu serumit itu. Jelek. Itu adalah kebutuhan yang sangat umum dalam pemrograman Web / JS. Metode baru untuk rilis bahasa berikutnya?
Andrew Koper
1
@AlbertoPerez, sama-sama !. Saludos hasta Madrid!
Christian C. Salvadó
126

Di es6 Anda bisa menggunakan sebagai berikut:

  • Operator penyebar

     var elements = [... nodelist]
  • Menggunakan Array.from

     var elements = Array.from(nodelist)

referensi lebih lanjut di https://developer.mozilla.org/en-US/docs/Web/API/NodeList

camiloazula.dll
sumber
4
sangat mudah dengan Array.from(): D
Josan Iracheta
4
jika seseorang menggunakan pendekatan ini dengan Typecript (ke ES5), hanya Array.fromberfungsi, karena TS mentransformasikannya ke nodelist.slice- yang tidak didukung.
Peter Albert
Saya menjawab hal yang sama setahun sebelumnya dan Anda memberikan saya suara? Saya tidak bisa menjelaskan ini ..
vsync
3
@vsync, jawaban Anda tidak menyebutkanArray.from
ESR
@EdmundReed - jadi? bagaimana itu membenarkannya. itu lebih lama untuk menulis jadi dalam situasi nyata tidak akan pernah bisa digunakan, hanya spreaddigunakan.
vsync
16

Menggunakan spread (ES2015) , semudah:[...document.querySelectorAll('p')]

(opsional: gunakan Babel untuk memindahkan kode ES6 di atas ke sintaks ES5)


Cobalah di konsol browser Anda dan lihat keajaibannya:

for( links of [...document.links] )
  console.log(links);
vsync
sumber
Setidaknya di chrome terbaru, 44, saya mendapatkan ini: Uncaught TypeError: document.querySelectorAll bukan fungsi (…)
Nick
@OmidHezaveh - Seperti yang saya katakan, ini adalah kode ES6. Saya tidak tahu apakah Chrome 44 mendukung ES6 dan jika demikian, pada cakupan apa. Ini browser yang hampir berumur satu tahun dan jelas Anda harus menjalankan kode ini di browser yang mendukung penyebaran ES6.
vsync
Atau kirimkan ke es5 sebelum eksekusi
HelloWorld
8

Gunakan trik sederhana ini

<Your array> = [].map.call(<Your dom array>, function(el) {
    return el;
})
Gena Shumilkin
sumber
Bisakah Anda menjelaskan mengapa menurut Anda ini memiliki peluang sukses yang lebih baik daripada menggunakan Array.prototype.slice(atau [].sliceseperti yang Anda katakan)? Sebagai catatan, saya ingin berkomentar bahwa kesalahan khusus IE yang saya dokumentasikan di Q terjadi di IE 8 atau lebih rendah, di mana maptetap tidak diterapkan. Di IE 9 ("mode standar") atau lebih tinggi, keduanya slicedan mapberhasil dengan cara yang sama.
Guss
6

Meskipun ini bukan tipuan yang tepat, karena tidak ada spesifikasi yang memerlukan pengerjaan elemen DOM, saya telah membuatnya untuk memungkinkan Anda menggunakan slice()dengan cara ini: https://gist.github.com/brettz9/6093105

PEMBARUAN : Ketika saya mengangkat ini dengan editor spesifikasi DOM4 (menanyakan apakah mereka mungkin menambahkan batasan mereka sendiri ke objek host (sehingga spesifikasi akan mengharuskan pelaksana untuk mengonversi objek ini dengan benar ketika digunakan dengan metode array) di luar spesifikasi ECMAScript yang diperbolehkan untuk implementasi-independensi), dia menjawab bahwa "Objek host kurang lebih sudah usang menurut ES6 / IDL." Saya melihat per http://www.w3.org/TR/WebIDL/#es-array bahwa spesifikasi dapat menggunakan IDL ini untuk mendefinisikan "objek larik platform" tetapi http://www.w3.org/TR/domcore/ tidak Sepertinya tidak menggunakan IDL baru untuk HTMLCollection(meskipun sepertinya IDL baru digunakan untuk Element.attributesitu meskipun hanya secara eksplisit menyatakan bahwa ia menggunakan WebIDL untuk DOMString dan DOMTimeStamp). Saya melihat[ArrayClass](yang mewarisi dari Array.prototype) digunakan untuk NodeList(dan NamedNodeMapsekarang tidak digunakan lagi untuk mendukung satu-satunya item yang masih akan menggunakannya, Element.attributes). Bagaimanapun, sepertinya itu akan menjadi standar. ES6 Array.frommungkin juga lebih nyaman untuk konversi seperti itu daripada harus menentukan Array.prototype.slicedan lebih jelas secara semantik daripada [].slice()(dan bentuk yang lebih pendek, Array.slice()("array generik"), sejauh yang saya tahu, tidak menjadi perilaku standar).

Brett Zamir
sumber
Saya telah memperbarui untuk menunjukkan bahwa spesifikasi mungkin bergerak ke arah yang mengharuskan perilaku ini.
Brett Zamir
5

Saat ini, di tahun 2018, kita dapat menggunakan ECMAScript 2015 (Edisi ke-6) atau ES6, tetapi tidak semua browser dapat memahaminya (misalnya IE tidak memahami semuanya). Jika mau, Anda bisa menggunakan ES6 sebagai berikut: var array = [... NodeList];( sebagai operator penyebaran ) atau var array = Array.from(NodeList);.

Dalam kasus lain (jika Anda tidak dapat menggunakan ES6), Anda dapat menggunakan cara terpendek untuk mengonversi a NodeListmenjadi Array:

var array = [].slice.call(NodeList, 0);.

Sebagai contoh:

var nodeList = document.querySelectorAll('input');
//we use "{}.toString.call(Object).slice(8, -1)" to find the class name of object
console.log({}.toString.call(nodeList).slice(8, -1)); //NodeList

var array = [].slice.call(nodeList, 0);
console.log({}.toString.call(array).slice(8, -1)); //Array

var result = array.filter(function(item){return item.value.length > 5});

for(var i in result)
  console.log(result[i].value); //credit, confidence
<input type="text" value="trust"><br><br>
<input type="text" value="credit"><br><br>
<input type="text" value="confidence">

Tetapi jika Anda ingin mengulang DOMdaftar node dengan mudah saja, maka Anda tidak perlu mengonversi a NodeListke an Array. Dimungkinkan untuk mengulang item dalam NodeListmenggunakan:

var nodeList = document.querySelectorAll('input');
// Calling nodeList.item(i) isn't necessary in JavaScript
for(var i = 0; i < nodeList.length; i++)
    console.log(nodeList[i].value); //trust, credit, confidence
<input type="text" value="trust"><br><br>
<input type="text" value="credit"><br><br>
<input type="text" value="confidence">

Jangan tergoda untuk menggunakan for...inatau for each...inmenghitung item dalam daftar, karena itu juga akan menghitung properti panjang dan item dari NodeListdan menyebabkan kesalahan jika skrip Anda menganggapnya hanya harus berurusan dengan objek elemen. Juga, for..intidak dijamin untuk mengunjungi properti dalam urutan tertentu. for...ofloop akan mengulang objek NodeList dengan benar.

Lihat juga:

Bharata
sumber
3
var arr = new Array();
var x= ... get your nodes;

for (i=0;i<x.length;i++)
{
  if (x.item(i).nodeType==1)
  {
    arr.push(x.item(i));
  }
}

Ini harus bekerja, lintas browser dan memberi Anda semua "elemen" node.

Strelok
sumber
1
Ini pada dasarnya sama dengan jawaban @ CMS, kecuali bahwa ini mengasumsikan saya hanya menginginkan node elemen - yang tidak saya inginkan.
Guss