วิธีที่เร็วที่สุดในการแปลง JavaScript NodeList เป็น Array?


251

คำถามที่ตอบก่อนหน้านี้ที่นี่บอกว่านี่เป็นวิธีที่เร็วที่สุด:

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

ในการเปรียบเทียบบนเบราว์เซอร์ของฉันฉันพบว่าช้ากว่านี้กว่า 3 เท่า:

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

พวกเขาทั้งสองให้ผลผลิตเดียวกัน แต่ฉันคิดว่ามันยากที่จะเชื่อว่ารุ่นที่สองของฉันเป็นวิธีที่เร็วที่สุดโดยเฉพาะอย่างยิ่งเมื่อมีคนพูดอย่างอื่นที่นี่

นี่เป็นเรื่องแปลกในเบราว์เซอร์ของฉัน (Chromium 6) หรือไม่ หรือมีวิธีที่เร็วขึ้น?

แก้ไข: สำหรับทุกคนที่ใส่ใจฉันตัดสินต่อไปนี้ (ซึ่งดูเหมือนจะเร็วที่สุดในทุกเบราว์เซอร์ที่ฉันทดสอบ):

//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++]));

แก้ไข 2: ฉันพบวิธีที่เร็วยิ่งขึ้น

// nl is the nodelist
var arr = [];
for(var i = nl.length; i--; arr.unshift(nl[i]));

2
arr[arr.length] = nl[i];อาจเร็วกว่าarr.push(nl[i]);เนื่องจากจะหลีกเลี่ยงการเรียกใช้ฟังก์ชัน
Luc125

9
หน้า jsPerf นี้ติดตามคำตอบทั้งหมดในหน้านี้: jsperf.com/nodelist-to-array/27
pilau

โปรดทราบว่า "EDIT2: ฉันพบวิธีที่เร็วกว่า" นั้นช้ากว่า 92% ใน IE8
Camilo Martin

2
เนื่องจากคุณรู้แล้วว่าคุณมีกี่โหนด:var i = nl.length, arr = new Array(i); for(; i--; arr[i] = nl[i]);
mems

@ Luc125 มันขึ้นอยู่กับเบราว์เซอร์เนื่องจากการใช้งานแบบพุชอาจได้รับการปรับให้เหมาะสมที่สุดฉันกำลังคิดเกี่ยวกับโครมเพราะ v8 ใช้ได้ดีกับสิ่งเหล่านี้
axelduch

คำตอบ:


197

อันที่สองมีแนวโน้มที่จะเร็วกว่าในเบราว์เซอร์บางตัว แต่ประเด็นหลักคือคุณต้องใช้เพราะเบราว์เซอร์แรกนั้นไม่ใช่เบราว์เซอร์ข้าม แม้ว่าพวกเขาจะเป็นเวลาของ Changin

@kangax (ดูตัวอย่าง IE 9 )

Array.prototype.sliceสามารถแปลงวัตถุโฮสต์บางตัว (เช่น NodeList) เป็นอาร์เรย์ซึ่งเป็นสิ่งที่เบราว์เซอร์สมัยใหม่ส่วนใหญ่สามารถทำได้ในขณะนี้

ตัวอย่าง:

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

??? ทั้งสองอย่างเข้ากันได้กับเบราว์เซอร์ - Javascript (อย่างน้อยถ้ามันอ้างว่าเข้ากันได้กับข้อกำหนด ECMAscript) คือ Javascript Array, Protice, Slice และ Call เป็นคุณสมบัติทั้งหมดของภาษาแกน + ประเภทวัตถุ
Jason S

6
แต่พวกเขาไม่สามารถใช้กับ NodeLists ใน IE ได้ (ฉันรู้ว่ามันแย่มาก แต่เดี๋ยวก่อนดูการอัปเดตของฉัน)
gblazex

9
เนื่องจาก NodeLists ไม่ได้เป็นส่วนหนึ่งของภาษาพวกเขาจึงเป็นส่วนหนึ่งของ DOM API ซึ่งเป็นที่รู้จักกันว่าเป็นรถ / คาดเดาไม่ได้โดยเฉพาะใน IE
gblazex

3
Array.prototype.slice ไม่ข้ามเบราว์เซอร์ถ้าคุณใช้ IE8 ในบัญชี
Lajos Meszaros

1
ใช่นั่นคือสิ่งที่คำตอบของฉันเป็นพื้นฐานเกี่ยวกับ :) แม้ว่ามันจะมีความเกี่ยวข้องมากขึ้นในปี 2010 มากกว่าในวันนี้ (2015)
gblazex

224

ด้วย ES6 ตอนนี้เรามีวิธีง่ายๆในการสร้าง Array จาก NodeList: the Array.from()function

// nl is a NodeList
let myArray = Array.from(nl)

วิธี ES6 ใหม่นี้เปรียบเทียบความเร็วกับวิธีอื่นที่ได้กล่าวไว้ข้างต้นได้อย่างไร
user5508297

10
@ user5508297 ดีกว่าเคล็ดลับการโทรแบบฝาน ช้ากว่าลูป แต่ก็ไม่แน่นอนว่าเราอาจต้องการอาเรย์โดยไม่ต้องข้ามมัน และไวยากรณ์นั้นสวยงามเรียบง่ายและจดจำได้ง่าย!
webdif

สิ่งที่ดีเกี่ยวกับ Array.from ก็คือคุณสามารถใช้การโต้แย้งแผนที่:console.log(Array.from([1, 2, 3], x => x + x)); // expected output: Array [2, 4, 6]
honzajde

103

นี่คือวิธีใหม่ที่ยอดเยี่ยมในการใช้ES6 Spread :

let arr = [...nl];

7
ไม่ได้ผลสำหรับฉันใน typescript ERROR TypeError: el.querySelectorAll(...).slice is not a function
Alireza Mirian

1
มาเร็วที่สุดเป็นอันดับ 3 โดยอยู่เบื้องหลัง 3 วิธีการยกเลิกการใช้งานโดยใช้ Chrome 71: jsperf.com/nodelist-to-array/90
BrianFreud

19

การเพิ่มประสิทธิภาพบางอย่าง:

  • บันทึกความยาวของ NodeList ในตัวแปร
  • กำหนดความยาวของอาเรย์ใหม่อย่างชัดเจนก่อนการตั้งค่า
  • เข้าถึงดัชนีแทนที่จะผลักหรือไม่ขยับ

รหัส ( jsPerf ):

var arr = [];
for (var i = 0, ref = arr.length = nl.length; i < ref; i++) {
 arr[i] = nl[i];
}

คุณสามารถโกนเล็ก ๆ น้อย ๆเวลามากขึ้นโดยใช้อาร์เรย์ (ความยาว) มากกว่าการสร้างอาร์เรย์แล้วแยกขนาดมัน ถ้าคุณใช้ const เพื่อประกาศอาร์เรย์และความยาวของมันด้วยการปล่อยให้อยู่ในลูปมันจะจบลงเร็วกว่าวิธีข้างต้นประมาณ 1.5%: const a = Array (nl.length), c = a.length; สำหรับ (ให้ b = 0; b <c; b ++) {a [b] = nl [b]; } see jsperf.com/nodelist-to-array/93
BrianFreud

15

ผลลัพธ์จะขึ้นอยู่กับเบราว์เซอร์อย่างสมบูรณ์เพื่อให้คำตัดสินตามวัตถุประสงค์เราต้องทำการทดสอบประสิทธิภาพบางอย่างนี่คือผลลัพธ์บางส่วนคุณสามารถเรียกใช้งานได้ที่นี่ :

Chrome 6:

Firefox 3.6:

Firefox 4.0b2:

Safari 5:

ดูตัวอย่างแพลตฟอร์ม IE9 3:


1
ฉันสงสัยว่าการวนกลับสำหรับการวนซ้ำกับสิ่งต่อไปนี้ ... for (var i=o.length; i--;)... 'for for loop' ในการทดสอบเหล่านี้ประเมินค่าคุณสมบัติความยาวของการวนซ้ำทุกครั้งหรือไม่
Dagg Nabbit

14

เบราว์เซอร์ที่เร็วและข้ามได้มากที่สุดคือ

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

อย่างที่ฉันเปรียบเทียบค่ะ

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

* ขอบคุณ @CMS สำหรับความคิด!

Chromium (คล้ายกับ Google Chrome) Firefox อุปรากร


1
ลิงก์ดูเหมือนว่าผิดควรเป็น 91 แทนที่จะเป็น 89 เพื่อรวมการทดสอบที่คุณพูดถึง และ 98 ดูเหมือนว่าสมบูรณ์ที่สุด
Yaroslav Yakovlev

9

ใน ES6 คุณสามารถใช้:

  • Array.from

    let array = Array.from(nodelist)

  • ผู้ประกอบการแพร่กระจาย

    let array = [...nodelist]


ไม่เพิ่มอะไรใหม่สิ่งที่เขียนไปแล้วในคำตอบก่อนหน้า
Stefan Becker

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

ตอนนี้คุณสามารถทำได้ document.querySelectorAll ('div') forEach (function () ... )


เป็นความคิดที่ดีขอบคุณ @John! อย่างไรก็ตามNodeListไม่ทำงาน แต่Objectเป็น: Object.prototype.forEach = Array.prototype.forEach; document.getElementsByTagName("img").forEach(function(img) { alert(img.src); });
Ian Campbell

3
อย่าใช้ Object.prototype: จะทำลาย JQuery และสิ่งต่างๆมากมายเช่นไวยากรณ์ตัวอักษรพจนานุกรม
Nate Symer

แน่นอนว่าหลีกเลี่ยงการขยายฟังก์ชั่นพื้นฐานในตัว
roland

5

เร็วและสั้น:

// nl is the nodelist
var a=[], l=nl.length>>>0;
for( ; l--; a[l]=nl[l] );

3
ทำไม>>>0? และทำไมไม่ใส่การบ้านไว้ในส่วนแรกของ for loop?
Camilo Martin

5
นอกจากนี้ยังเป็นบั๊กกี้ เมื่อlใดคือ0ลูปจะสิ้นสุดดังนั้น0องค์ประกอบที่สองจะไม่ถูกคัดลอก (จำได้ว่ามีองค์ประกอบอยู่ที่ดัชนี0)
Camilo Martin

1
รักคำตอบนี้ แต่ ... ทุกคนที่สงสัย: สิ่งที่>>> อาจไม่จำเป็นที่นี่ แต่ใช้เพื่อรับประกันความยาวของ nodelist เป็นไปตามข้อกำหนดอาร์เรย์; มันทำให้แน่ใจว่ามันเป็นจำนวนเต็ม 32 บิตที่ไม่ได้ลงนาม ลองใช้งานได้ที่นี่ecma-international.org/ecma-262/5.1/#sec-15.4หากคุณชอบรหัสที่อ่านไม่ได้ให้ใช้วิธีนี้กับคำแนะนำของ @ CamiloMartin!
ทอดด์

ในการตอบกลับถึง @CamiloMartin - มีความเสี่ยงที่จะใส่varเข้าไปในส่วนแรกของforลูปเนื่องจาก 'การเลือกตัวแปร' การประกาศvarจะเป็น 'ยก' ขึ้นไปด้านบนของฟังก์ชั่นแม้ว่าvarบรรทัดจะปรากฏที่ใดที่หนึ่งที่ต่ำลงและอาจทำให้เกิดผลข้างเคียงที่ไม่ชัดเจนจากลำดับรหัส ตัวอย่างเช่นบางโค้ดในฟังก์ชันเดียวกันที่เกิดขึ้นก่อนหน้า for loop อาจขึ้นอยู่กับaและlไม่ได้ประกาศ ดังนั้นเพื่อความสามารถในการทำนายที่มากขึ้นให้ประกาศ vars ของคุณที่ด้านบนของฟังก์ชั่น (หรือถ้าใช้ ES6 ให้ใช้constหรือletแทนซึ่งไม่ได้ยก)
brennanyoung

3

ลองอ่านโพสต์บล็อกนี้ที่นี่ซึ่งพูดถึงเรื่องเดียวกัน จากสิ่งที่ฉันรวบรวมเวลาเพิ่มเติมอาจต้องเกี่ยวข้องกับการเดินขึ้นห่วงโซ่ขอบเขต


น่าสนใจ ฉันเพิ่งทำการทดสอบที่คล้ายกันในขณะนี้และ Firefox 3.6.3 ไม่แสดงว่าความเร็วเพิ่มขึ้นอย่างใดอย่างหนึ่งในขณะที่ Opera 10.6 มีการเพิ่มขึ้น 20% และ Chrome 6 มีการเพิ่มขึ้น 230% (!) ด้วยวิธีการทำซ้ำแบบแมนนวล
jairajs89

@ jairajs89 ค่อนข้างแปลก ดูเหมือนว่ามันขึ้นArray.prototype.sliceอยู่กับเบราว์เซอร์ ฉันสงสัยว่าอัลกอริทึมแต่ละเบราว์เซอร์ที่ใช้อยู่
Vivin Paliath


1

นี่คือแผนภูมิที่อัปเดต ณ วันที่โพสต์นี้ (แผนภูมิ "แพลตฟอร์มที่ไม่รู้จัก" คือ Internet Explorer 11.15.16299.0):

Safari 11.1.2 Firefox 61.0 Chrome 68.0.3440.75 Internet Explorer 11.15.16299.0

จากผลลัพธ์เหล่านี้ดูเหมือนว่าวิธี preallocate 1 เป็นการเดิมพันข้ามเบราว์เซอร์ที่ปลอดภัยที่สุด


1

สมมติว่าnodeList = document.querySelectorAll("div")นี่เป็นรูปแบบที่กระชับของการแปลงnodelistเป็นอาเรย์

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

เห็นฉันใช้มันนี่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.