Cara tercepat untuk mengonversi NodeList JavaScript ke Array?

251

Pertanyaan yang sebelumnya dijawab di sini mengatakan bahwa ini adalah cara tercepat:

//nl is a NodeList
var arr = Array.prototype.slice.call(nl);

Dalam melakukan pembandingan pada browser saya, saya menemukan bahwa ini lebih dari 3 kali lebih lambat dari ini:

var arr = [];
for(var i = 0, n; n = nl[i]; ++i) arr.push(n);

Keduanya menghasilkan output yang sama, tetapi saya merasa sulit untuk percaya bahwa versi kedua saya adalah cara tercepat yang mungkin, terutama karena orang mengatakan sebaliknya di sini.

Apakah ini kekhasan di browser saya (Chromium 6)? Atau ada cara yang lebih cepat?

EDIT: Untuk siapa pun yang peduli, saya memilih yang berikut (yang tampaknya paling cepat di setiap browser yang saya uji):

//nl is a NodeList
var l = []; // Will hold the array of Node's
for(var i = 0, ll = nl.length; i != ll; l.push(nl[i++]));

EDIT2: Saya menemukan cara yang lebih cepat

// nl is the nodelist
var arr = [];
for(var i = nl.length; i--; arr.unshift(nl[i]));
jairajs89
sumber
2
arr[arr.length] = nl[i];mungkin lebih cepat daripada arr.push(nl[i]);karena menghindari panggilan fungsi.
Luc125
9
Halaman jsPerf ini mencatat semua jawaban di halaman ini: jsperf.com/nodelist-to-array/27
pilau
Harap dicatat bahwa "EDIT2: Saya menemukan cara yang lebih cepat" adalah 92% lebih lambat pada IE8.
Camilo Martin
2
Karena Anda tahu sudah tahu berapa banyak node yang Anda miliki:var i = nl.length, arr = new Array(i); for(; i--; arr[i] = nl[i]);
mems
@ Luc125 Tergantung pada browser, karena implementasi push dapat dioptimalkan, saya berpikir tentang chrome karena v8 bagus dengan hal-hal semacam ini.
axelduch

Jawaban:

197

Yang kedua cenderung lebih cepat di beberapa browser, tetapi poin utamanya adalah Anda harus menggunakannya karena yang pertama bukan cross-browser. Meskipun Masa Mereka Berubah

@kangax ( pratinjau IE 9 )

Array.prototype.slice sekarang dapat mengonversi objek host tertentu (mis. NodeList) menjadi array - sesuatu yang sebagian besar browser modern mampu lakukan selama beberapa saat.

Contoh:

Array.prototype.slice.call(document.childNodes);
gblazex
sumber
??? Keduanya kompatibel lintas-browser - Javascript (setidaknya jika ia mengklaim kompatibel dengan spesifikasi ECMAscript) adalah Javascript; Array, prototype, slice, dan call adalah semua fitur dari bahasa inti + tipe objek.
Jason S
6
tetapi mereka tidak dapat digunakan pada NodeLists di IE (Saya tahu itu menyebalkan, tapi hei lihat pembaruan saya)
gblazex
9
karena NodeLists bukan bagian dari bahasa, mereka adalah bagian dari DOM API, yang dikenal buggy / tidak dapat diprediksi terutama di IE
gblazex
3
Array.prototype.slice bukan browser lintas, jika Anda mengambil IE8 di akun.
Lajos Meszaros
1
Ya, pada dasarnya itulah jawaban saya tentang :) Meskipun lebih relevan di 2010 daripada hari ini (2015).
gblazex
224

Dengan ES6, kita sekarang memiliki cara sederhana untuk membuat Array dari NodeList: Array.from()fungsi.

// nl is a NodeList
let myArray = Array.from(nl)
webdif
sumber
Bagaimana metode ES6 baru ini membandingkan kecepatannya dengan yang lain yang telah disebutkan di atas?
user5508297
10
@ user5508297 Lebih baik daripada trik panggilan slice. Lebih lambat dari untuk loop, tapi itu bukan kita mungkin ingin memiliki array tanpa melewatinya. Dan sintaksinya indah, sederhana dan mudah diingat!
webdif
Hal yang menyenangkan tentang Array.from adalah bahwa Anda dapat menggunakan argumen peta:console.log(Array.from([1, 2, 3], x => x + x)); // expected output: Array [2, 4, 6]
honzajde
103

Berikut cara keren baru untuk melakukannya menggunakan operator penyebaran ES6 :

let arr = [...nl];
Alexander Olsson
sumber
7
Tidak bekerja untuk saya dalam naskah. ERROR TypeError: el.querySelectorAll(...).slice is not a function
Alireza Mirian
1
Hadir tercepat ke-3, di belakang 3 metode unshift, menggunakan Chrome 71: jsperf.com/nodelist-to-array/90
BrianFreud
19

Beberapa optimasi:

  • simpan panjang NodeList dalam sebuah variabel
  • secara eksplisit mengatur panjang array baru sebelum pengaturan.
  • mengakses indeks, alih-alih mendorong atau melepaskan.

Kode ( jsPerf ):

var arr = [];
for (var i = 0, ref = arr.length = nl.length; i < ref; i++) {
 arr[i] = nl[i];
}
Thai
sumber
Anda dapat mencukur sedikit lebih banyak waktu dengan menggunakan Array (panjang) bukan menciptakan array dan kemudian secara terpisah ukuran itu. Jika Anda kemudian menggunakan const untuk mendeklarasikan array dan panjangnya, dengan sebuah let di dalam loop, ia berakhir sekitar 1,5% lebih cepat dari metode di atas: const a = Array (nl.length), c = a.length; untuk (misalkan b = 0; b <c; b ++) {a [b] = nl [b]; } lihat jsperf.com/nodelist-to-array/93
BrianFreud
15

Hasilnya akan sepenuhnya tergantung pada browser, untuk memberikan putusan yang obyektif, kita harus membuat beberapa tes kinerja, berikut adalah beberapa hasil, Anda dapat menjalankannya di sini :

Chrome 6:

Firefox 3.6:

Firefox 4.0b2:

Safari 5:

Pratinjau Platform IE9 3:

CMS
sumber
1
Saya bertanya-tanya bagaimana kebalikan dari loop bertahan terhadap ini ... for (var i=o.length; i--;)... apakah 'untuk loop' dalam tes ini mengevaluasi ulang properti panjang pada setiap iterasi?
Dagg Nabbit
14

Browser paling cepat dan lintas adalah

for(var i=-1,l=nl.length;++i!==l;arr[i]=nl[i]);

Seperti yang saya membandingkan di

http://jsbin.com/oqeda/98/edit

* Terima kasih @CMS untuk idenya!

Chromium (Mirip dengan Google Chrome) Firefox Opera

Felipe Buccioni
sumber
1
tampaknya tautan salah, harus 91, bukan 89 untuk memasukkan tes yang Anda sebutkan. Dan 98 tampaknya yang paling lengkap.
Yaroslav Yakovlev
9

Di ES6 Anda dapat menggunakan:

  • Dari Array

    let array = Array.from(nodelist)

  • Operator yang tersebar

    let array = [...nodelist]

Isabella
sumber
Tidak menambahkan sesuatu yang baru apa yang sudah ditulis dalam jawaban sebelumnya.
Stefan Becker
7
NodeList.prototype.forEach = Array.prototype.forEach;

Sekarang Anda dapat melakukan document.querySelectorAll ('div'). ForEach (function () ...)

John Williams
sumber
Ide bagus, terima kasih @ John! Namun, NodeListtidak berfungsi tetapi Object: Object.prototype.forEach = Array.prototype.forEach; document.getElementsByTagName("img").forEach(function(img) { alert(img.src); });
Ian Campbell
3
Jangan gunakan Object.prototype: itu memecah JQuery dan banyak hal seperti sintaks literal kamus.
Nate Symer
Tentu, hindari untuk memperluas fungsi bawaan bawaan.
roland
5

lebih cepat dan lebih pendek:

// nl is the nodelist
var a=[], l=nl.length>>>0;
for( ; l--; a[l]=nl[l] );
anonim
sumber
3
Mengapa >>>0? Dan mengapa tidak menempatkan tugas pada bagian pertama dari for loop?
Camilo Martin
5
Juga, ini buggy. Ketika ladalah 0, loop akan berakhir, oleh karena itu 0elemen th tidak akan disalin (ingat ada unsur pada indeks 0)
Camilo Martin
1
Suka jawaban ini, tapi ... Siapa pun yang bertanya-tanya: >>> mungkin tidak diperlukan di sini tetapi digunakan untuk menjamin panjang nodelist itu mematuhi array spec; itu memastikan bahwa itu adalah bilangan bulat 32-bit yang tidak ditandatangani. Lihat di sini ecma-international.org/ecma-262/5.1/#sec-15.4 Jika Anda menyukai kode yang tidak dapat dibaca, gunakan metode ini dengan saran @ CamiloMartin!
Todd
Sebagai balasan untuk @CamiloMartin - Beresiko untuk dimasukkan ke vardalam bagian pertama forloop karena 'pengangkatan variabel'. Deklarasi varakan 'diangkat' ke atas fungsi, bahkan jika vargaris muncul di suatu tempat lebih rendah, dan ini dapat menyebabkan efek samping yang tidak jelas dari urutan kode. Misalnya beberapa kode dalam fungsi yang sama yang terjadi sebelum for for loop mungkin bergantung pada adan ltidak dideklarasikan. Karena itu untuk kemungkinan yang lebih besar, nyatakan vars Anda di bagian atas fungsi (atau jika pada ES6, gunakan constatau letsebaliknya, yang tidak mengangkat).
brennanyoung
3

Lihat posting blog ini di sini yang membahas hal yang sama. Dari apa yang saya kumpulkan, waktu tambahan mungkin ada hubungannya dengan berjalan di rantai lingkup.

Vivin Paliath
sumber
Menarik. Saya baru saja melakukan beberapa tes serupa sekarang dan Firefox 3.6.3 tidak menunjukkan peningkatan kecepatan dalam melakukan keduanya, sementara Opera 10.6 memiliki peningkatan 20% dan Chrome 6 memiliki peningkatan 230% (!) Dengan melakukannya secara manual dengan cara push-iterate.
jairajs89
@ jairajs89 cukup aneh. Tampaknya Array.prototype.slicetergantung pada browser. Saya ingin tahu algoritma apa yang digunakan oleh masing-masing browser.
Vivin Paliath
3

Ini adalah fungsi yang saya gunakan di JS saya:

function toArray(nl) {
    for(var a=[], l=nl.length; l--; a[l]=nl[l]);
    return a;
}
Desainer web
sumber
1

Berikut adalah bagan yang diperbarui pada tanggal posting ini (bagan "platform tidak dikenal" adalah Internet Explorer 11.15.16299.0):

Safari 11.1.2 Firefox 61.0 Chrome 68.0.3440.75 Internet Explorer 11.15.16299.0

Dari hasil ini, tampaknya metode preallocate 1 adalah taruhan lintas-browser yang paling aman.

TomSlick
sumber
1

Dengan asumsi nodeList = document.querySelectorAll("div"), ini adalah bentuk ringkas dari konversi nodelistke array.

var nodeArray = [].slice.call(nodeList);

Lihat saya menggunakannya di sini .

Udo E.
sumber