ทำไม nodelist ไม่มี forEach?


93

ฉันกำลังทำงานกับสคริปต์สั้น ๆ เพื่อเปลี่ยน<abbr>ข้อความภายในขององค์ประกอบ แต่พบว่าnodelistไม่มีforEachวิธีการ ฉันรู้ว่านั่นnodelistไม่ได้สืบทอดมาArrayแต่ดูเหมือนforEachจะเป็นวิธีที่มีประโยชน์หรือไม่ มีปัญหาการใช้งานโดยเฉพาะอย่างยิ่งผมไม่ได้ตระหนักถึงการป้องกันที่เพิ่มforEachไปnodelist?

หมายเหตุ: ฉันทราบว่า Dojo และ jQuery ทั้งคู่มีforEachรูปแบบบางอย่างสำหรับ nodelists ของพวกเขา ฉันไม่สามารถใช้ได้เนื่องจากข้อ จำกัด


6
สวัสดีจากอนาคต! nodeList มี forEach ตั้งแต่ ES6
Blaise

คำตอบ:


96

ตอนนี้ NodeList มี forEach () ในเบราว์เซอร์หลักทั้งหมด

ดูnodeList forEach () บน MDN

คำตอบเดิม

ไม่มีคำตอบใดที่อธิบายว่าเหตุใด NodeList จึงไม่สืบทอดจาก Array จึงทำให้มีforEachและส่วนที่เหลือทั้งหมด

พบคำตอบในกระทู้สนทนานี้ ในระยะสั้นมันทำลายเว็บ:

ปัญหาคือโค้ดที่สันนิษฐานว่าอินสแตนซ์ไม่ถูกต้องซึ่งหมายความว่าอินสแตนซ์นั้นเป็น Array ร่วมกับ Array.prototype.concat

มีข้อบกพร่องในไลบรารีการปิดของ Google ซึ่งทำให้แอปเกือบทั้งหมดของ Google ล้มเหลวเนื่องจากเหตุนี้ ไลบรารีได้รับการอัปเดตทันทีที่พบสิ่งนี้ แต่อาจยังมีโค้ดอยู่ที่นั่นซึ่งทำให้สมมติฐานที่ไม่ถูกต้องเหมือนกันเมื่อใช้ร่วมกับ concat

นั่นคือโค้ดบางตัวทำบางอย่างเช่น

if (x instanceof Array) {
  otherArray.concat(x);
} else {
  doSomethingElseWith(x);
}

อย่างไรก็ตามconcatจะถือว่าอาร์เรย์ "จริง" (ไม่ใช่อินสแตนซ์ของอาร์เรย์) แตกต่างจากวัตถุอื่น ๆ :

[1, 2, 3].concat([4, 5, 6]) // [1, 2, 3, 4, 5, 6]
[1, 2, 3].concat(4) // [1, 2, 3, 4]

นั่นหมายความว่าโค้ดด้านบนพังเมื่อxเป็น NodeList เพราะก่อนที่มันจะไปตามdoSomethingElseWith(x)เส้นทางในขณะที่หลังจากนั้นมันก็ไปตามotherArray.concat(x)เส้นทางซึ่งทำอะไรแปลก ๆ เนื่องจากxไม่ใช่อาร์เรย์จริง

บางครั้งมีข้อเสนอสำหรับElementsคลาสที่เป็นคลาสย่อยจริงของ Array และจะใช้เป็น "NodeList ใหม่" อย่างไรก็ตามสิ่งนี้ได้ถูกลบออกจาก DOM Standardแล้วอย่างน้อยก็ในตอนนี้เนื่องจากยังไม่สามารถใช้งานได้ด้วยเหตุผลทางเทคนิคและข้อกำหนดที่เกี่ยวข้องหลายประการ


11
ดูเหมือนเป็นการโทรหาฉันที่ไม่ดี อย่างจริงจังฉันคิดว่าเป็นการตัดสินใจที่ถูกต้องที่จะทำลายสิ่งต่าง ๆ เป็นครั้งคราวโดยเฉพาะอย่างยิ่งหากนั่นหมายความว่าเรามี API ที่ดีสำหรับอนาคต นอกจากนี้ยังไม่เหมือนกับว่าเว็บใกล้จะเป็นแพลตฟอร์มที่มีความเสถียรผู้คนคุ้นเคยกับจาวาสคริปต์อายุ 2 ปีไม่ทำงานตามที่คาดไว้อีกต่อไป ..
JoyalToTheWorld

3
"Breaks the web"! = "ทำลายเนื้อหาของ Google"
Matt

ทำไมไม่ลองยืมเมธอด forEach จาก Array.prototype ล่ะ เช่นแทนที่จะเพิ่ม Array เป็นต้นแบบให้ทำเช่นนี้forEach = Array.prototype.forEach ในตัวสร้างหรือแม้แต่ใช้ forEach แบบไม่ซ้ำกันสำหรับ NodeList?
PopKernel

2
บันทึก; การอัปเดตนี้NodeList now has forEach() in all major browsersดูเหมือนจะบอกเป็นนัยว่า IE ไม่ใช่เบราว์เซอร์หลัก หวังว่าจะเป็นจริงสำหรับบางคน แต่มันไม่จริงสำหรับฉัน (ยัง)
Graham P Heath

58

คุณทำได้

Array.prototype.forEach.call (nodeList, function (node) {

    // Your code here.

} );

3
Array.prototype.forEach.callสามารถย่อมาที่[].forEach.call
CodeBrauer

7
@CodeBrauer: นั่นไม่ใช่แค่การทำให้สั้นลงArray.prototype.forEach.callเท่านั้น แต่ยังสร้างอาร์เรย์ว่างเปล่าและใช้forEachวิธีการ
Paul D. Waite

34

คุณสามารถพิจารณาสร้างอาร์เรย์ของโหนดใหม่

  var nodeList = document.getElementsByTagName('div'),

      nodes = Array.prototype.slice.call(nodeList,0); 

  // nodes is an array now.
  nodes.forEach(function(node){ 

       // do your stuff here.  

  });

หมายเหตุ: นี่เป็นเพียงรายการ / อาร์เรย์ของการอ้างอิงโหนดที่เรากำลังสร้างขึ้นที่นี่ไม่มีโหนดที่ซ้ำกัน

  nodes[0] === nodeList[0] // will be true

22
Array.prototype.forEach.call(nodeList, fun)หรือเพียงแค่ทำ
akuhn

ฉันยังอยากจะแนะนำ aliasing ฟังก์ชั่น forEach var forEach = Array.prototype.forEach.call(nodeList, callback);ดังกล่าวว่า: ตอนนี้คุณสามารถโทรforEach(nodeList, callback);
Andrew Craswell

19

อย่าพูดเลยมันเป็นปี 2016 และNodeListวัตถุได้ใช้forEachวิธีการใน chrome ล่าสุด (v52.0.2743.116)

ยังเร็วเกินไปที่จะใช้ในการผลิตเนื่องจากเบราว์เซอร์อื่นยังไม่รองรับสิ่งนี้ (ทดสอบ FF 49) แต่ฉันเดาว่าสิ่งนี้จะเป็นมาตรฐานในไม่ช้า


2
Opera ยังสนับสนุนและจะเพิ่มการสนับสนุนใน v50 ของ Firefox ซึ่งมีกำหนดวางจำหน่ายในวันที่ 15/11/59
Shaggy

1
แม้ว่าจะนำมาใช้ แต่ก็ไม่ได้เป็นส่วนหนึ่งของมาตรฐานใด ๆ วิธีที่ดีที่สุดArray.prototype.slice.call(nodelist).forEach(…)คือทำแบบมาตรฐานและใช้ได้กับเบราว์เซอร์รุ่นเก่า
เนท

18

กล่าวโดยย่อคือความขัดแย้งในการออกแบบที่จะใช้วิธีการนั้น

จาก MDN:

เหตุใดฉันจึงใช้ forEach หรือ map บน NodeList ไม่ได้

NodeList ถูกนำมาใช้อย่างมากเช่นอาร์เรย์และจะเป็นการดึงดูดที่จะใช้เมธอด Array.prototype กับพวกเขา อย่างไรก็ตามนี่เป็นไปไม่ได้

JavaScript มีกลไกการสืบทอดตามต้นแบบ อินสแตนซ์อาร์เรย์สืบทอดเมธอดอาร์เรย์ (เช่น forEach หรือ map) เนื่องจากห่วงโซ่ต้นแบบมีลักษณะดังต่อไปนี้:

myArray --> Array.prototype --> Object.prototype --> null (สามารถรับห่วงโซ่ต้นแบบของวัตถุได้โดยการเรียก Object.getPrototypeOf หลาย ๆ ครั้ง)

forEach แผนที่และสิ่งที่ชอบเป็นคุณสมบัติของวัตถุ Array.prototype

แตกต่างจากอาร์เรย์ห่วงโซ่ต้นแบบ NodeList มีลักษณะดังนี้:

myNodeList --> NodeList.prototype --> Object.prototype --> null

NodeList.prototype มีเมธอดไอเท็ม แต่ไม่มีเมธอด Array.prototype ดังนั้นจึงไม่สามารถใช้กับ NodeLists ได้

ที่มา: https://developer.mozilla.org/en-US/docs/DOM/NodeList (เลื่อนลงไปที่Why can't I use forEach or map on a NodeList? )


8
ดังนั้นเนื่องจากเป็นรายการทำไมจึงออกแบบมาเช่นนั้น? อะไรที่หยุดพวกเขาให้สร้างห่วงโซ่: myNodeList --> NodeList.prototype --> Array.prototype --> Object.prototype --> null?
Igor Pantović

14

หากคุณต้องการใช้ forEach บน NodeList เพียงแค่คัดลอกฟังก์ชันนั้นจาก Array:

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

นั่นคือทั้งหมดนี้คุณสามารถใช้งานได้ในลักษณะเดียวกับที่คุณใช้กับ Array:

document.querySelectorAll('td').forEach(function(o){
   o.innerHTML = 'text';
});

4
ยกเว้นว่าการกลายพันธุ์ของคลาสพื้นฐานจะไม่ชัดเจนสำหรับผู้อ่านโดยเฉลี่ย กล่าวอีกนัยหนึ่งลึก ๆ ในโค้ดบางโค้ดคุณต้องจำทุกการปรับแต่งที่คุณทำกับวัตถุพื้นฐานของเบราว์เซอร์ การใช้เอกสาร MDN จะไม่มีประโยชน์อีกต่อไปเนื่องจากอ็อบเจ็กต์มีพฤติกรรมเปลี่ยนไปจากบรรทัดฐาน เป็นการดีกว่าที่จะใช้ต้นแบบอย่างชัดเจนในเวลาโทรเพื่อให้ผู้อ่านสามารถเข้าใจได้ง่ายว่า forEach เป็นแนวคิดที่ยืมมาและไม่ใช่สิ่งที่เป็นส่วนหนึ่งของนิยามภาษา ดูคำตอบที่ @akuhn ด้านบน
Sukima

@ สุกิมะเข้าใจผิดโดยผิดสถานที่. ในกรณีเฉพาะนี้แนวทางที่กำหนดจะแก้ไข NodeList ให้ทำงานตามที่นักพัฒนาคาดไว้ นี่เป็นวิธีที่เหมาะสมที่สุดในการแก้ไขปัญหาคลาสของระบบ (ดูเหมือนว่า NodeList จะยังไม่เสร็จสิ้นและควรได้รับการแก้ไขในภาษาในอนาคต)
Daniel Garmoshka

1
ตอนนี้ NodeList.forEach มีอยู่แล้วนี่จะกลายเป็นโพลีฟิลล์ที่เรียบง่ายเฮฮา
Damon

7

ใน ES2015 ตอนนี้คุณสามารถใช้forEachmethod ไปยัง nodeList ได้แล้ว

document.querySelectorAll('abbr').forEach( el => console.log(el));

ดูลิงก์ MDN

อย่างไรก็ตามหากคุณต้องการใช้ HTML Collections หรือวัตถุอื่น ๆ ที่มีลักษณะคล้ายอาร์เรย์ใน es2015 คุณสามารถใช้Array.from()method วิธีนี้ใช้วัตถุที่เหมือนอาร์เรย์หรือทำซ้ำได้ (รวมถึง nodeList, HTML Collections, strings เป็นต้น) และส่งคืนอินสแตนซ์ Array ใหม่ คุณสามารถใช้งานได้ดังนี้:

const elements = document.getElementsByTagName('abbr');
Array.from(elements).forEach( el => console.log(el));

เนื่องจากArray.from()วิธีการเป็น shimmable คุณสามารถใช้ในรหัส es5 เช่นนี้

var elements = document.getElementsByTagName('abbr');
Array.from(elements).forEach( function(el) {
    console.log(el);
});

สำหรับรายละเอียดโปรดดูหน้าMDN

ในการตรวจสอบการสนับสนุนเบราว์เซอร์ในปัจจุบัน

หรือ

อีกวิธีหนึ่งของ es2015 คือการใช้ตัวดำเนินการกระจาย

[...document.querySelectorAll('abbr')].forEach( el => console.log(el));

ตัวดำเนินการแพร่กระจาย MDN

Spread Operator - การสนับสนุนเบราว์เซอร์


2

วิธีแก้ปัญหาของฉัน:

//foreach for nodeList
NodeList.prototype.forEach = Array.prototype.forEach;
//foreach for HTML collection(getElementsByClassName etc.)
HTMLCollection.prototype.forEach = Array.prototype.forEach;

1
มักไม่ใช่ความคิดที่ดีที่จะขยายการทำงานของ DOM ผ่านต้นแบบโดยเฉพาะอย่างยิ่งใน IE เวอร์ชันเก่า ( บทความ )
KFE

0

NodeList เป็นส่วนหนึ่งของ DOM API ดูการผูก ECMAScript ซึ่งใช้กับ JavaScript ด้วย http://www.w3.org/TR/DOM-Level-2-Core/ecma-script-binding.html nodeList และคุณสมบัติความยาวแบบอ่านอย่างเดียวและฟังก์ชัน item (index) เพื่อส่งคืนโหนด

คำตอบคือคุณต้องทำซ้ำ ไม่มีทางเลือกอื่น Foreach จะไม่ทำงาน ฉันทำงานกับการเชื่อมโยง Java DOM API และมีปัญหาเดียวกัน


แต่มีเหตุผลพิเศษไหมที่ไม่ควรนำมาใช้? ทั้ง jQuery และ Dojo ได้นำไปใช้ในห้องสมุดของตัวเอง
Snakes and Coffee

2
แต่มันมีความขัดแย้งในการออกแบบอย่างไร?
Snakes and Coffee

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