สำหรับลูปสำหรับองค์ประกอบ HTMLCollection


405

HTMLCollectionOfฉันพยายามที่จะได้รับรหัสชุดขององค์ประกอบทั้งหมดใน ฉันเขียนรหัสต่อไปนี้:

var list = document.getElementsByClassName("events");
console.log(list[0].id);
for (key in list) {
    console.log(key.id);
}

แต่ฉันได้ผลลัพธ์ต่อไปนี้ใน console:

event1
undefined

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


23
ทำไมชื่อถึงพูดว่า "foreach" เมื่อคำถามเกี่ยวกับ for ... in statement ฉันมาที่นี่โดยบังเอิญเมื่อ googling
mxt3

@ mxt3 ในความเข้าใจของฉันมันเป็น anologue ของแต่ละวงใน Java (ทำงานเหมือนกัน)

1
@ mxt3 ฉันคิดเช่นเดียวกัน! แต่หลังจากอ่านคำตอบที่ยอมรับฉันพบบรรทัดนี้ซึ่งแก้ไขปัญหา foreach ของฉันโดยใช้ Array.from ():Array.from(document.getElementsByClassName("events")).forEach(function(item) {
HoldOffHunger

คำตอบ:


839

เพื่อตอบคำถามเดิมคุณใช้งานfor/inไม่ถูกต้อง ในรหัสของคุณkeyคือดัชนี ดังนั้นเพื่อให้ได้ค่าจากหลอกอาร์เรย์ที่คุณจะต้องทำและจะได้รับประจำตัวประชาชนของคุณควรจะทำlist[key] list[key].idแต่คุณไม่ควรทำสิ่งนี้for/inในตอนแรก

สรุป (เพิ่มในธันวาคม 2018)

ห้ามใช้for/inเพื่อทำซ้ำ nodeList หรือ HTMLCollection เหตุผลในการหลีกเลี่ยงดังอธิบายไว้ด้านล่าง

ทุกรุ่นล่าสุดของเบราว์เซอร์ที่ทันสมัย (Safari, Firefox, Chrome, EDGE) ทุกการสนับสนุนfor/ofการทำซ้ำในรายการ DOM ดังกล่าวหรือnodeListHTMLCollection

นี่คือตัวอย่าง:

var list = document.getElementsByClassName("events");
for (let item of list) {
    console.log(item.id);
}

หากต้องการรวมเบราว์เซอร์รุ่นเก่า (รวมถึงสิ่งต่างๆเช่น IE) สิ่งนี้จะทำงานได้ทุกที่:

var list= document.getElementsByClassName("events");
for (var i = 0; i < list.length; i++) {
    console.log(list[i].id); //second console output
}

คำอธิบายว่าทำไมคุณไม่ควรใช้ for/in

for/inมีความหมายสำหรับการวนซ้ำคุณสมบัติของวัตถุ นั่นหมายความว่ามันจะส่งคืนคุณสมบัติที่วนซ้ำได้ของวัตถุ แม้ว่ามันอาจจะใช้งานได้กับอาเรย์ (การส่งคืนองค์ประกอบอาเรย์หรือองค์ประกอบอาเรย์หลอก) แต่ก็สามารถส่งคืนคุณสมบัติอื่น ๆ ของวัตถุที่ไม่ใช่สิ่งที่คุณคาดหวังจากองค์ประกอบแบบอาเรย์ และเดาว่าอะไรHTMLCollectionหรือnodeListวัตถุใดที่สามารถมีคุณสมบัติอื่นที่จะถูกส่งคืนพร้อมการfor/inวนซ้ำ ฉันเพิ่งลองมันใน Chrome และวนซ้ำในแบบที่คุณทำซ้ำมันจะเรียกคืนรายการในรายการ (ดัชนี 0, 1, 2, ฯลฯ ... ) แต่จะดึงlengthและitemคุณสมบัติ การfor/inทำซ้ำจะไม่ทำงานกับ HTMLCollection


ดูhttp://jsfiddle.net/jfriend00/FzZ2H/สำหรับเหตุผลที่คุณไม่สามารถย้ำ HTMLCollection for/inกับ

ใน Firefox for/inการทำซ้ำของคุณจะส่งคืนรายการเหล่านี้ (คุณสมบัติที่ทำซ้ำได้ทั้งหมดของวัตถุ):

0
1
2
item
namedItem
@@iterator
length

หวังว่าตอนนี้คุณสามารถดูว่าทำไมคุณต้องการที่จะใช้for (var i = 0; i < list.length; i++)แทนดังนั้นคุณก็จะได้รับ0, 1และ2ในการทำซ้ำของคุณ


การติดตามด้านล่างนี้เป็นวิวัฒนาการของวิธีที่เบราว์เซอร์มีวิวัฒนาการตลอดช่วงเวลาระหว่าง 2558-2561 ซึ่งจะทำให้คุณสามารถทำซ้ำ ไม่จำเป็นต้องใช้สิ่งเหล่านี้ในเบราว์เซอร์สมัยใหม่เนื่องจากคุณสามารถใช้ตัวเลือกที่อธิบายไว้ข้างต้น

อัพเดทสำหรับ ES6 ในปี 2558

เพิ่มใน ES6 คือArray.from()จะแปลงโครงสร้างคล้ายอาร์เรย์เป็นอาร์เรย์จริง ที่อนุญาตให้หนึ่งแจกแจงรายการโดยตรงเช่นนี้

"use strict";

Array.from(document.getElementsByClassName("events")).forEach(function(item) {
   console.log(item.id);
});

สาธิตการทำงาน (ใน Firefox, Chrome และ Edge ณ เดือนเมษายน 2559): https://jsfiddle.net/jfriend00/8ar4xn2s/


อัปเดตสำหรับ ES6 ในปี 2559

ตอนนี้คุณสามารถใช้ ES6 สำหรับ / ของการสร้างกับNodeListและHTMLCollectionโดยเพียงแค่เพิ่มสิ่งนี้ในรหัสของคุณ:

NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];

จากนั้นคุณสามารถ:

var list = document.getElementsByClassName("events");
for (var item of list) {
    console.log(item.id);
}

สามารถใช้งานได้กับ Chrome, Firefox และ Edge รุ่นปัจจุบัน สิ่งนี้ทำงานได้เพราะมันแนบ Array iterator กับทั้ง NodeList และ HTMLCollection prototypes ดังนั้นเมื่อสำหรับ / ของ iterates เหล่านั้นมันจะใช้ Arer iterator เพื่อย้ำพวกมัน

การสาธิตการทำงาน: http://jsfiddle.net/jfriend00/joy06u4e/


การอัปเดตครั้งที่สองสำหรับ ES6 ในเดือนธันวาคม 2559

ตั้งแต่เดือนธันวาคม 2559 Symbol.iteratorการสนับสนุนได้ติดตั้งไว้ใน Chrome v54 และ Firefox v50 ดังนั้นโค้ดด้านล่างจึงสามารถใช้งานได้ด้วยตัวเอง มันยังไม่ได้ติดตั้งไว้สำหรับ Edge

var list = document.getElementsByClassName("events");
for (let item of list) {
    console.log(item.id);
}

สาธิตการทำงาน (ใน Chrome และ Firefox): http://jsfiddle.net/jfriend00/3ddpz8sp/

การอัพเดทครั้งที่สามสำหรับ ES6 ในเดือนธันวาคม 2017

ณ ธันวาคม 2017, ความสามารถนี้ทำงานในขอบ 41.16299.15.0 สำหรับnodeListในขณะที่document.querySelectorAll()แต่ไม่ได้เป็นHTMLCollectionเช่นเดียวกับในdocument.getElementsByClassName()ดังนั้นคุณต้องกำหนด iterator HTMLCollectionที่จะใช้ในขอบด้วยตนเอง มันเป็นความลึกลับโดยรวมว่าทำไมพวกเขาถึงต้องแก้ไขคอลเล็กชันประเภทหนึ่ง แต่ไม่ใช่ประเภทอื่น แต่อย่างน้อยคุณสามารถใช้ผลลัพธ์ของการdocument.querySelectorAll()ใช้for/ofไวยากรณ์ES6 ในเวอร์ชันปัจจุบันของ Edge ได้ทันที

ผมได้ปรับปรุงยังด้านบน jsFiddle จึงทดสอบทั้งสองHTMLCollectionและnodeListแยกจากกันและจับเอาท์พุทใน jsFiddle เอง

การอัพเดทครั้งที่สี่สำหรับ ES6 ในเดือนมีนาคม 2018

ต่อ mesqueeeb, Symbol.iteratorการสนับสนุนได้รับการสร้างขึ้นในซาฟารีเกินไปดังนั้นคุณจึงสามารถใช้for (let item of list)อย่างใดอย่างหนึ่งหรือdocument.getElementsByClassName()document.querySelectorAll()

การอัพเดทครั้งที่ห้าสำหรับ ES6 ในเดือนเมษายน 2018

เห็นได้ชัดว่าการสนับสนุนการทำซ้ำHTMLCollectionด้วยfor/ofจะมาถึง Edge 18 ในฤดูใบไม้ร่วง 2018

การอัพเดทครั้งที่หกสำหรับ ES6 ในเดือนพฤศจิกายน 2018

ฉันสามารถยืนยันได้ด้วย Microsoft Edge v18 (ซึ่งรวมอยู่ใน Fall 2018 Windows Update) ตอนนี้คุณสามารถทำซ้ำทั้ง HTMLCollection และ NodeList ด้วย for / of in Edge

ดังนั้นตอนนี้เบราว์เซอร์ที่ทันสมัยทั้งหมดมีการสนับสนุนเนทิฟสำหรับfor/ofการวนซ้ำของทั้งวัตถุ HTMLCollection และ NodeList


1
ขอบคุณสำหรับการอัปเดตที่มีรายละเอียดมากเนื่องจาก JS ได้อัปเดตแล้ว สิ่งนี้ช่วยให้ผู้เริ่มต้นเข้าใจคำแนะนำนี้ในบริบทของบทความ / โพสต์อื่น ๆ ที่ใช้กับรีลีส JS เฉพาะเท่านั้น
brownmagik352

คำตอบที่โดดเด่นการสนับสนุนการอัปเดตที่น่าทึ่ง .. ขอบคุณ
cossacksman

79

คุณไม่สามารถใช้for/ inบนNodeListหรือHTMLCollections อย่างไรก็ตามคุณสามารถใช้บางส่วนArray.prototypeวิธีการตราบเท่าที่คุณ.call()พวกเขาและผ่านในNodeListหรือเป็นHTMLCollectionthis

ดังนั้นให้พิจารณาสิ่งต่อไปนี้เป็นทางเลือกแทนการวนซ้ำของ jfriend00for :

var list= document.getElementsByClassName("events");
[].forEach.call(list, function(el) {
    console.log(el.id);
});

มีบทความที่ดีเกี่ยวกับ MDNที่ครอบคลุมเทคนิคนี้ สังเกตคำเตือนเกี่ยวกับความเข้ากันได้ของเบราว์เซอร์ว่า:

[... ] การส่งผ่านวัตถุโฮสต์ (เช่น a NodeList) เป็น thisวิธีดั้งเดิม (เช่นforEach) ไม่รับประกันว่าจะทำงานในเบราว์เซอร์ทั้งหมดและเป็นที่รู้กันว่าล้มเหลวในบางส่วน

ดังนั้นในขณะที่วิธีนี้สะดวก แต่การforวนซ้ำอาจเป็นโซลูชันที่เข้ากันได้กับเบราว์เซอร์มากที่สุด

อัปเดต (30 ส.ค. 2014):ในที่สุดคุณจะสามารถใช้ES6 for/of !

var list = document.getElementsByClassName("events");
for (const el of list)
  console.log(el.id);

รองรับแล้วใน Chrome และ Firefox เวอร์ชันล่าสุด


1
ดีมาก! <select multiple>ผมใช้เทคนิคนี้จะได้รับค่าตัวเลือกเลือกจากที่ ตัวอย่าง:[].map.call(multiSelect.selectedOptions, function(option) { return option.value; })
XåpplI'-I0llwlg'I -

1
ฉันกำลังมองหาโซลูชัน ES2015 สำหรับสิ่งนี้ดังนั้นขอขอบคุณสำหรับการยืนยันว่าได้for ... ofผล
Richard Turner

60

ใน ES6 คุณสามารถทำสิ่งที่ชอบ[...collection]หรือArray.from(collection),

let someCollection = document.querySelectorAll(someSelector)
[...someCollection].forEach(someFn) 
//or
Array.from(collection).forEach(someFn)

เช่น:-

    navDoms = document.getElementsByClassName('nav-container');
    Array.from(navDoms).forEach(function(navDom){
     //implement function operations
    });

@DanielM เดาสิ่งที่ฉันได้ทำคือตื้นโคลนอาร์เรย์เหมือนโครงสร้าง
mido

ฉันเห็นแล้วขอบคุณ - ตอนนี้ฉันได้พบเอกสารที่ฉันกำลังมองหา: developer.mozilla.org/en/docs/Web/JavaScript/Reference/ …
DanielM

ฉันมักใช้สิ่งนี้ง่ายต่อสายตามากกว่า Array จากนั้นฉันแค่สงสัยว่ามันมีประสิทธิภาพหรือข้อบกพร่องของหน่วยความจำมาก ตัวอย่างเช่นถ้าฉันต้องย้ำเซลล์แถวของตารางฉันใช้ a [...row.cells].forEachแทนrow.querySelectorAll('td')
Mojimi

16

คุณสามารถเพิ่มสองบรรทัดนี้:

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

HTMLCollectionถูกส่งคืนโดยgetElementsByClassNameและgetElementsByTagName

NodeListถูกส่งคืนโดยquerySelectorAll

เช่นนี้คุณสามารถทำ forEach:

var selections = document.getElementsByClassName('myClass');

/* alternative :
var selections = document.querySelectorAll('.myClass');
*/

selections.forEach(function(element, i){
//do your stuffs
});

คำตอบนี้ดูเหมือนจะมีประสิทธิภาพ จับคืออะไร?
Peheje

2
ที่จับคือว่าวิธีนี้ไม่ทำงานบน IE11! ทางออกที่ดีแม้ว่า
ราหุลกาบา

2
โปรดทราบว่ามีอยู่แล้วNodeList forEach()
Franklin Yu

7

ฉันมีปัญหาในการใช้ forEach ในIE 11และFirefox 49

ฉันพบวิธีแก้ปัญหาเช่นนี้

Array.prototype.slice.call(document.getElementsByClassName("events")).forEach(function (key) {
        console.log(key.id);
    }

ทางออกที่ดีสำหรับ IE11! เคยเป็นเทคนิคทั่วไป ...
mb21

6

ทางเลือกที่Array.fromจะใช้Array.prototype.forEach.call

แต่ละ: Array.prototype.forEach.call(htmlCollection, i => { console.log(i) });

แผนที่: Array.prototype.map.call(htmlCollection, i => { console.log(i) });

ฯลฯ...


6

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

ใน ES5 มีสองตัวเลือกที่ดี ครั้งแรกที่คุณสามารถ "ยืม" Array's forEachเป็นEvan กล่าวกล่าว

แต่ยิ่งดีกว่า ...

การใช้งานObject.keys()ที่ไม่ได้forEachและตัวกรองเพื่อ "คุณสมบัติของตัวเอง" โดยอัตโนมัติ

นั่นคือObject.keysเป็นหลักเทียบเท่ากับการทำfor... inมีHasOwnPropertyแต่มากนุ่มนวล

var eventNodes = document.getElementsByClassName("events");
Object.keys(eventNodes).forEach(function (key) {
    console.log(eventNodes[key].id);
});

5

ตั้งแต่เดือนมีนาคม 2559 ใน Chrome 49.0 ใช้for...ofงานได้สำหรับHTMLCollection:

this.headers = this.getElementsByTagName("header");

for (var header of this.headers) {
    console.log(header); 
}

ดูที่นี่เอกสาร

แต่จะใช้งานได้หากคุณใช้วิธีแก้ปัญหาต่อไปนี้ก่อนที่จะใช้for...of:

HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];

จำเป็นต้องใช้for...ofกับNodeList:

NamedNodeMap.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];

ฉันเชื่อ / หวังว่าfor...ofเร็ว ๆ นี้จะทำงานโดยไม่มีวิธีแก้ปัญหาข้างต้น ปัญหาเปิดอยู่ที่นี่:

https://bugs.chromium.org/p/chromium/issues/detail?id=401699

อัปเดต:ดูความคิดเห็นของ Expenzor ด้านล่าง: สิ่งนี้ได้รับการแก้ไขแล้วในเดือนเมษายน 2559 คุณไม่จำเป็นต้องเพิ่ม HTMLCollection.prototype [Symbol.iterator] = Array.prototype [Symbol.iterator]; เพื่อวนซ้ำ HTMLCollection ด้วยสำหรับ ... จาก


4
นี้ได้รับการแก้ไขในเดือนเมษายน 2016 คุณไม่จำเป็นต้องเพิ่มHTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];ย้ำกว่าด้วยHTMLCollection for...of
Expenzor

3

บนขอบ

if(!NodeList.prototype.forEach) {
  NodeList.prototype.forEach = function(fn, scope) {
    for(var i = 0, len = this.length; i < len; ++i) {
      fn.call(scope, this[i], i, this);
    }
  }
}

2

วิธีแก้ปัญหาง่ายๆที่ฉันใช้อยู่เสมอ

let list = document.getElementsByClassName("events");
let listArr = Array.from(list)

หลังจากนี้คุณสามารถเรียกใช้วิธีอาร์เรย์ใด ๆ ที่ต้องการในการเลือก

listArr.map(item => console.log(item.id))
listArr.forEach(item => console.log(item.id))
listArr.reverse()


0

คุณต้องการเปลี่ยนเป็น

var list= document.getElementsByClassName("events");
console.log(list[0].id); //first console output
for (key in list){
    console.log(list[key].id); //second console output
}

5
FYI โปรดดูคำตอบของฉันว่าทำไมสิ่งนี้จึงไม่ทำงาน for (key in list)จะกลับมาหลายคุณสมบัติของHTMLCollectionที่ไม่ได้หมายถึงการเป็นสินค้าที่อยู่ในคอลเลกชัน
jfriend00
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.