วิธีการแปลงรายการโหนด DOM เป็นอาร์เรย์ใน Javascript


103

ฉันมีฟังก์ชั่น Javascript ที่ยอมรับรายการโหนด HTML แต่คาดว่าจะมีอาร์เรย์ Javascript (มันเรียกใช้เมธอด Array บางอย่างในนั้น) และฉันต้องการฟีดผลลัพธ์ของDocument.getElementsByTagNameสิ่งนั้นที่ส่งคืนรายการโหนด DOM

ตอนแรกฉันคิดว่าจะใช้อะไรง่ายๆเช่น:

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

และทำงานได้ดีในทุกเบราว์เซอร์ยกเว้น Internet Explorer ซึ่งส่งคืนข้อผิดพลาด "วัตถุ JScript ที่คาดไว้" เนื่องจากรายการโหนด DOM ที่ส่งคืนโดยDocument.getElement*วิธีการไม่ใช่วัตถุ JScript เพียงพอที่จะเป็นเป้าหมายของการเรียกฟังก์ชัน

คำเตือน: ฉันไม่สนใจที่จะเขียนโค้ดเฉพาะของ Internet Explorer แต่ฉันไม่ได้รับอนุญาตให้ใช้ไลบรารี Javascript เช่น JQuery เนื่องจากฉันกำลังเขียนวิดเจ็ตที่จะฝังลงในเว็บไซต์ของบุคคลที่สามและฉันไม่สามารถโหลดไลบรารีภายนอกที่ จะสร้างความขัดแย้งให้กับลูกค้า

ความพยายามสุดท้ายของฉันคือการทำซ้ำรายการโหนด DOM และสร้างอาร์เรย์ด้วยตัวเอง แต่มีวิธีที่ดีกว่าในการทำเช่นนั้นหรือไม่?


ยังดีกว่าสร้างฟังก์ชันเพื่อแปลงจากรายการโหนด DOM แต่นั่นจะเป็นทางออกของฉันจริงๆฉันคิดว่าคุณเข้าใจถูกแล้ว
Kristoffer Sall-Storgaard

> for (i = 0; i & lt; x.length; i ++) ทำไมต้องรับความยาวของ NodeList ทุกครั้งที่ทำซ้ำ ไม่เพียงเสียเวลา แต่เนื่องจาก NodeLists เป็นคอลเล็กชันแบบสดหากมีสิ่งใดในเนื้อหาของลูปเปลี่ยนความยาวคุณสามารถวนซ้ำได้ไม่รู้จบหรือตีดัชนีนอกขอบเขตได้ อย่างหลังเป็นสิ่งที่เลวร้ายที่สุดที่อาจเกิดขึ้นได้หากคุณกำหนดความยาวให้กับตัวแปรและข้อผิดพลาดนั้นดีกว่าการวนซ้ำที่ไม่สิ้นสุด

นี่เป็นคำถามเก่าจริงๆ แต่ jQuery ถูกสร้างขึ้นด้วยเมธอด. noConflictโดยเฉพาะดังนั้นมันจะไม่ก่อให้เกิดความขัดแย้งกับไลบรารีอื่น ๆ (แม้แต่ตัวมันเอง) ซึ่งหมายความว่าสามารถโหลด jQuery หลายเวอร์ชันบนหน้าได้ ที่กล่าวว่าดีที่สุดคือหลีกเลี่ยงการใช้ / โหลดไลบรารีเว้นแต่คุณจะต้องทำอย่างนั้น
vol7ron

@ vol7ron: ก้าวไปข้างหน้าอย่างรวดเร็วถึงปี 2016 และทุกคนยังคงกังวลเกี่ยวกับขนาดที่ไลบรารีจาวาสคริปต์เพิ่มลงในเพจ จริงอยู่ที่ JQuery ย่อขนาดและ gzipped คือ 30KB แต่ยังคงเป็น 30KB มากเกินไปที่จะแปลงรายการโหนด :-)
Guss

คำตอบ:


71

NodeLists เป็นอ็อบเจ็กต์โฮสต์โดยใช้Array.prototype.sliceเมธอดบนอ็อบเจ็กต์โฮสต์ไม่รับประกันว่าจะทำงานสถานะข้อกำหนด ECMAScript:

ฟังก์ชัน slice สามารถนำไปใช้กับอ็อบเจ็กต์โฮสต์ได้สำเร็จหรือไม่นั้นขึ้นอยู่กับการนำไปใช้งาน

ฉันขอแนะนำให้คุณสร้างฟังก์ชันง่ายๆเพื่อวนซ้ำNodeListและเพิ่มองค์ประกอบที่มีอยู่ในอาร์เรย์:

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;
}

อัพเดท:

ตามที่คำตอบอื่นแนะนำตอนนี้คุณสามารถใช้ไวยากรณ์การแพร่กระจายหรือArray.fromวิธีการ:

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

แต่เมื่อคิดเกี่ยวกับเรื่องนี้ฉันเดาว่ากรณีการใช้งานที่พบบ่อยที่สุดในการแปลง NodeList เป็น Array คือการวนซ้ำและตอนนี้NodeList.prototypeวัตถุมีforEachวิธีการโดยกำเนิดดังนั้นหากคุณอยู่ในสภาพแวดล้อมที่ทันสมัยคุณสามารถใช้งานได้โดยตรงหรือมี โพลีฟิล


2
นี่คือการสร้างอาร์เรย์โดยให้ลำดับเดิมของรายการย้อนกลับซึ่งฉันไม่คิดว่าเป็นสิ่งที่ OP ต้องการ คุณหมายถึงทำarray[i] = obj[i]แทนarray.push(obj[i])หรือไม่?
Tim Down

@ ทิมใช่ฉันเคยเป็นแบบนั้นมาก่อน แต่แก้ไขเมื่อคืนวานนี้โดยไม่ได้สังเกตเห็นมัน (เวลาท้องถิ่น 3:00 น. :) ขอบคุณ!.
Christian C.Salvadó

9
ในกรณีใดที่จะobj.lengthเป็นอย่างอื่นแล้วค่าจำนวนเต็ม?
ปีเตอร์

1
ฉันไม่อยากจะเชื่อเลยว่ามันซับซ้อนขนาดนั้น น่าเกลียด. นั่นเป็นความต้องการทั่วไปในการเขียนโปรแกรม Web / JS วิธีการใหม่สำหรับภาษารุ่นต่อไป?
Andrew Koper

1
@AlbertoPerez ยินดีต้อนรับ!. Saludos hasta Madrid!
Christian C. Salvadó

133

ในes6คุณสามารถใช้ดังนี้:

  • ตัวดำเนินการกระจาย

     var elements = [... nodelist]
    
  • การใช้ Array.from

     var elements = Array.from(nodelist)
    

อ้างอิงเพิ่มเติมที่https://developer.mozilla.org/en-US/docs/Web/API/NodeList


4
ง่ายมากกับArray.from(): D
Josan Iracheta

4
ในกรณีที่มีคนใช้แนวทางนี้กับ typescript (ถึง ES5) จะArray.fromใช้ได้เฉพาะเนื่องจาก TS ส่งข้อมูลนี้ไปnodelist.sliceซึ่งไม่รองรับ
Peter Albert

ฉันตอบเหมือนกันหนึ่งปีก่อนคุณและคุณให้คะแนนฉัน? อธิบายไม่ถูก ..
vsync

3
@vsync คำตอบของคุณไม่ได้กล่าวถึงArray.from
ESR

@EdmundReed - เหรอ? มันจะพิสูจน์ได้อย่างไร มันยาวกว่าที่จะเขียนดังนั้นในสถานการณ์จริงมันจะไม่ถูกนำมาใช้ แต่spreadจะใช้เท่านั้น
vsync

17

การใช้สเปรด (ES2015)ทำได้ง่ายเหมือนกับ:[...document.querySelectorAll('p')]

(ทางเลือก: ใช้Babelเพื่อถ่ายทอดรหัส ES6 ด้านบนเป็นไวยากรณ์ ES5)


ลองใช้ในคอนโซลของเบราว์เซอร์และดูความมหัศจรรย์:

for( links of [...document.links] )
  console.log(links);

อย่างน้อยอย่างน้อย chrome ล่าสุด 44 ฉันได้รับสิ่งนี้: Uncaught TypeError: document.querySelectorAll ไม่ใช่ฟังก์ชัน (…)
Nick

@OmidHezaveh - อย่างที่บอกนี่คือรหัส ES6 ฉันไม่รู้ว่า Chrome 44 รองรับ ES6 หรือไม่และถ้าเป็นเช่นนั้นจะครอบคลุมระดับใด เป็นเบราว์เซอร์ที่มีอายุเกือบหนึ่งปีและแน่นอนว่าคุณจะต้องเรียกใช้รหัสนี้บนเบราว์เซอร์ที่รองรับการแพร่กระจาย ES6
vsync

หรือย้ายไปที่ es5 ก่อนดำเนินการ
HelloWorld

8

ใช้เคล็ดลับง่ายๆนี้

<Your array> = [].map.call(<Your dom array>, function(el) {
    return el;
})

คุณช่วยอธิบายได้ไหมว่าทำไมคุณถึงคิดว่าสิ่งนี้มีโอกาสประสบความสำเร็จมากกว่าการใช้Array.prototype.slice(หรือ[].sliceตามที่คุณวางไว้) ตามหมายเหตุฉันต้องการแสดงความคิดเห็นว่าข้อผิดพลาดเฉพาะของ IE ที่ฉันบันทึกไว้ใน Q นั้นเกิดขึ้นใน IE 8 หรือต่ำกว่าโดยที่mapยังไม่มีการนำไปใช้ ใน IE 9 ("โหมดมาตรฐาน") หรือสูงกว่าทั้งสองอย่างsliceและmapประสบความสำเร็จในลักษณะเดียวกัน
Guss

6

แม้ว่าจะไม่ใช่ shim ที่เหมาะสมจริงๆเนื่องจากไม่มีข้อมูลจำเพาะที่ต้องการการทำงานกับองค์ประกอบ DOM แต่ฉันได้สร้างขึ้นเพื่อให้คุณใช้slice()ในลักษณะนี้: https://gist.github.com/brettz9/6093105

อัปเดต : เมื่อฉันยกสิ่งนี้ขึ้นด้วยตัวแก้ไขข้อมูลจำเพาะ DOM4 (ถามว่าพวกเขาอาจเพิ่มข้อ จำกัด ของตัวเองในโฮสต์อ็อบเจ็กต์หรือไม่ (เพื่อที่ข้อมูลจำเพาะจะต้องให้ผู้ดำเนินการแปลงอ็อบเจ็กต์เหล่านี้อย่างถูกต้องเมื่อใช้กับเมธอดอาร์เรย์) เกินข้อกำหนด ECMAScript ซึ่งมี อนุญาตให้ใช้งานได้อย่างอิสระ) เขาตอบว่า "โฮสต์อ็อบเจ็กต์ล้าสมัยมากหรือน้อยต่อ ES6 / IDL" ฉันเห็นตามhttp://www.w3.org/TR/WebIDL/#es-arrayว่าข้อกำหนดสามารถใช้ IDL นี้เพื่อกำหนด "วัตถุอาร์เรย์แพลตฟอร์ม" แต่http://www.w3.org/TR/domcore/ doesn ดูเหมือนว่าจะใช้ IDL ใหม่สำหรับHTMLCollection(แม้ว่าจะดูเหมือนว่าอาจทำเช่นนั้นElement.attributesแม้ว่าจะระบุอย่างชัดเจนว่าใช้ WebIDL สำหรับ DOMString และ DOMTimeStamp) ฉันเห็น[ArrayClass](ซึ่งสืบทอดมาจาก Array.prototype) ใช้สำหรับNodeList(และNamedNodeMapตอนนี้เลิกใช้แล้วเพื่อสนับสนุนรายการเดียวที่ยังคงใช้อยู่Element.attributes) ไม่ว่าในกรณีใดดูเหมือนว่าจะกลายเป็นมาตรฐาน ES6 Array.fromอาจสะดวกกว่าสำหรับการแปลงดังกล่าวมากกว่าที่จะต้องระบุArray.prototype.sliceและมีความชัดเจนมากกว่า[].slice()(และรูปแบบที่สั้นกว่าArray.slice()("อาร์เรย์ทั่วไป") มีเท่าที่ฉันทราบไม่ใช่พฤติกรรมมาตรฐาน)


ฉันได้อัปเดตเพื่อระบุว่าข้อมูลจำเพาะอาจเคลื่อนไปในทิศทางที่ต้องการพฤติกรรมนี้
Brett Zamir

5

วันนี้ในปี 2018 เราสามารถใช้ ECMAScript 2015 (รุ่นที่ 6) หรือ ES6 ได้ แต่มีบางเบราว์เซอร์เท่านั้นที่เข้าใจได้ (เช่น IE ไม่เข้าใจทั้งหมด) หากคุณต้องการคุณสามารถใช้ ES6 ดังต่อไปนี้: var array = [... NodeList];( เป็นตัวดำเนินการกระจาย ) หรือvar array = Array.from(NodeList);.

ในกรณีอื่น (ถ้าคุณไม่สามารถใช้ ES6) คุณสามารถใช้วิธีที่สั้นที่สุดในการแปลง a NodeListเป็นArray:

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

ตัวอย่างเช่น:

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">

แต่ถ้าคุณต้องการวนซ้ำDOMรายการโหนดอย่างง่าย ๆ เท่านั้นคุณไม่จำเป็นต้องแปลง a NodeListเป็นArrayไฟล์. เป็นไปได้ที่จะวนซ้ำรายการNodeListโดยใช้:

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">

อย่าถูกล่อลวงให้ใช้for...inหรือfor each...inระบุรายการในรายการเนื่องจากจะแจกแจงความยาวและคุณสมบัติของรายการNodeListและทำให้เกิดข้อผิดพลาดหากสคริปต์ของคุณถือว่าต้องจัดการกับออบเจ็กต์องค์ประกอบเท่านั้น นอกจากนี้for..inไม่รับประกันว่าจะเยี่ยมชมคุณสมบัติตามลำดับใด ๆ for...ofลูปจะวนซ้ำบนวัตถุ NodeList อย่างถูกต้อง

ดูด้วย:


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));
  }
}

วิธีนี้ควรใช้งานข้ามเบราว์เซอร์และรับโหนด "องค์ประกอบ" ทั้งหมด


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