JS: วนซ้ำผลลัพธ์ของ getElementsByClassName โดยใช้ Array.forEach


240

ฉันต้องการวนซ้ำองค์ประกอบ DOM บางอย่างฉันกำลังทำสิ่งนี้:

document.getElementsByClassName( "myclass" ).forEach( function(element, index, array) {
  //do stuff
});

แต่ฉันได้รับข้อผิดพลาด:

document.getElementsByClassName ("myclass") forEach ไม่ใช่ฟังก์ชัน

ฉันใช้ Firefox 3 ดังนั้นฉันรู้ว่าทั้งสองgetElementsByClassNameและArray.forEachมีอยู่ ใช้งานได้ดี:

[2, 5, 9].forEach( function(element, index, array) {
  //do stuff
});

ผลลัพธ์ของgetElementsByClassNameArray คืออะไร? ถ้าไม่มันคืออะไร

คำตอบ:


384

ไม่ตามที่ระบุใน DOM4เป็นHTMLCollection(ในเบราว์เซอร์รุ่นใหม่อย่างน้อยเบราว์เซอร์รุ่นเก่าจะส่งคืนNodeList)

ในเบราว์เซอร์สมัยใหม่ทั้งหมด (มีอะไรอื่น ๆ อีกมากมายอย่าง IE <= 8) คุณสามารถเรียกforEachใช้เมธอดของ Array ส่งผ่านรายการองค์ประกอบ (ไม่ว่าจะHTMLCollectionหรือNodeList) เป็นthisค่า:

var els = document.getElementsByClassName("myclass");

Array.prototype.forEach.call(els, function(el) {
    // Do stuff here
    console.log(el.tagName);
});

// Or
[].forEach.call(els, function (el) {...});

หากคุณอยู่ในตำแหน่งที่มีความสุขที่สามารถใช้ ES6 (เช่นคุณสามารถเพิกเฉยต่อ Internet Explorer ได้อย่างปลอดภัยหรือคุณกำลังใช้ ES5 transpiler) คุณสามารถใช้Array.from:

Array.from(els).forEach((el) => {
    // Do stuff here
    console.log(el.tagName);
});

29
ไม่จำเป็นต้องแปลงเป็น Array ก่อน [].forEach.call(elsArray, function () {...})ใช้เพียงแค่
kay - SE ชั่วร้าย

1
ไม่ใช่ NodeList มันเป็นวัตถุแบบอาเรย์ ฉันไม่คิดว่ามันจะมีประเภทอินสแตนซ์ querySelectorAllวิธีการส่งกลับ NodeList แม้ว่า
Maksim Vi

2
@MaksimVi คุณพูดถูก: DOM4 ระบุว่าdocument.getElementsByClassName()ควรส่งคืนHTMLCollection(ซึ่งคล้ายกันมาก แต่ไม่ใช่ NodeList) ขอบคุณสำหรับการชี้ให้เห็นข้อผิดพลาด
Tim Down

@ MaksimVi: ฉันสงสัยว่ามันเปลี่ยนแปลงไปบ้างไหม ฉันมักจะตรวจสอบสิ่งเหล่านี้
Tim Down

1
@ TimDown ขอบคุณสำหรับHTMLCollectionเคล็ดลับ ในที่สุดฉันก็สามารถใช้HTMLCollection.prototype.forEach = Array.prototype.forEach;ในรหัสของฉันได้
Maksim Vi

70

คุณสามารถใช้Array.fromเพื่อแปลงคอลเล็กชันเป็นอาร์เรย์ซึ่งเป็นวิธีที่สะอาดกว่าArray.prototype.forEach.call:

Array.from(document.getElementsByClassName("myclass")).forEach(
    function(element, index, array) {
        // do stuff
    }
);

ในเบราว์เซอร์รุ่นเก่าที่ไม่รองรับArray.fromคุณต้องใช้บางอย่างเช่น Babel


ES6 ยังเพิ่มไวยากรณ์นี้:

[...document.getElementsByClassName("myclass")].forEach(
    (element, index, array) => {
        // do stuff
    }
);

การทำลายโครงสร้างด้วยการ...ทำงานบนวัตถุที่มีลักษณะเหมือนอาร์เรย์ทั้งหมดไม่เพียง แต่จัดเรียงตัวเองแล้วใช้ไวยากรณ์อาร์เรย์แบบเก่าที่ดีเพื่อสร้างอาร์เรย์จากค่าต่างๆ


ในขณะที่ฟังก์ชั่นทางเลือกquerySelectorAll(อันไหนที่ทำให้getElementsByClassNameล้าสมัย) จะส่งกลับคอลเลกชันที่มีอยู่ตามforEachปกติวิธีอื่นเช่นmapหรือfilterหายไปดังนั้นไวยากรณ์นี้ยังมีประโยชน์:

[...document.querySelectorAll(".myclass")].map(
    (element, index, array) => {
        // do stuff
    }
);

[...document.querySelectorAll(".myclass")].map(element => element.innerHTML);

6
หมายเหตุ: หากไม่มี transpilling ตามที่แนะนำ (Babel) สิ่งนี้ไม่สามารถใช้งานร่วมกับ IE <Edge, Opera, Safari <9, เบราว์เซอร์ Android, Chrome สำหรับ Android, ... ฯลฯ ) ที่มา: mozilla dev docs
Sean

30

หรือคุณสามารถใช้querySelectorAllที่ส่งคืนNodeList :

document.querySelectorAll('.myclass').forEach(...)

สนับสนุนโดยเบราว์เซอร์ที่ทันสมัย ​​(รวมถึง Edge แต่ไม่ใช่ IE):
ฉันสามารถใช้ querySelectorAll NodeList.prototype.forEach () ได้ไหม

MDN: Document.querySelectorAll ()


4
จำไว้ว่าต้องเสียค่าปรับมากกว่า getElementByClassName
Szabolcs Páll

3
การลงโทษประสิทธิภาพนั้นเล็กน้อยเมื่อเทียบกับงานที่ต้องใช้ความพยายามมากขึ้นเช่นการแก้ไข DOM หากฉันดำเนินการ60,000 ของเหล่านี้ใน 1 มิลลิวินาทีฉันค่อนข้างแน่ใจว่ามันจะไม่เป็นปัญหาสำหรับการใช้งานที่เหมาะสม :)
249152

1
คุณเชื่อมโยงมาตรฐานที่ไม่ถูกต้อง นี่คือหนึ่งวัดที่ถูกต้องThat.net/Benchmarks/Show/4076/0/เพียงแค่วิ่งบนโทรศัพท์ต่ำสุดของฉันได้รับ 160k / s vs 380k / s เนื่องจากคุณพูดถึงการจัดการ DOM นี่ก็เป็นเช่นกันmeasurethat.net/Benchmarks/Show/5705/0/…มี 50k / s เทียบกับ 130k / s อย่างที่คุณเห็นมันช้ากว่าในการจัดการ DOM น่าจะเกิดจาก NodeList เป็นแบบสแตติก (ดังที่คนอื่น ๆ พูดถึง) ยังใช้งานได้ในเกือบทุกกรณี แต่ช้าลงเกือบ 3 เท่า
Szabolcs Páll

14

แก้ไข: แม้ว่าประเภทการส่งคืนจะเปลี่ยนเป็น HTML เวอร์ชันใหม่ (ดูคำตอบที่อัปเดตล่าสุดของ Tim Down) แต่รหัสด้านล่างยังใช้ได้

อย่างที่คนอื่น ๆ พูดกันมันคือ NodeList นี่คือตัวอย่างการทำงานที่สมบูรณ์ที่คุณสามารถลองได้:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <script>
            function findTheOddOnes()
            {
                var theOddOnes = document.getElementsByClassName("odd");
                for(var i=0; i<theOddOnes.length; i++)
                {
                    alert(theOddOnes[i].innerHTML);
                }
            }
        </script>
    </head>
    <body>
        <h1>getElementsByClassName Test</h1>
        <p class="odd">This is an odd para.</p>
        <p>This is an even para.</p>
        <p class="odd">This one is also odd.</p>
        <p>This one is not odd.</p>
        <form>
            <input type="button" value="Find the odd ones..." onclick="findTheOddOnes()">
        </form>
    </body>
</html>

สามารถใช้งานได้ใน IE 9, FF 5, Safari 5 และ Chrome 12 ใน Win 7


9

ผลมาจากการgetElementsByClassName()ไม่ได้เป็นอาร์เรย์ แต่วัตถุอาร์เรย์เหมือน โดยเฉพาะมันถูกเรียกว่าHTMLCollection, ไม่ต้องสับสนNodeList( ซึ่งมีforEach()วิธีการของตัวเอง )

วิธีง่ายๆในหนึ่งเดียวกับ ES2015 การแปลงอาร์เรย์เหมือนวัตถุสำหรับใช้กับArray.prototype.forEach()ที่ไม่ได้รับการกล่าวถึงเลยคือการใช้ผู้ประกอบการแพร่กระจายหรือการแพร่กระจายไวยากรณ์ :

const elementsArray = document.getElementsByClassName('myclass');

[...elementsArray].forEach((element, index, array) => {
    // do something
});

2
ฉันรู้สึกว่านี่เป็นวิธีที่ถูกต้องในเบราว์เซอร์สมัยใหม่ นี่คือการใช้ไวยากรณ์การแพร่กระจายกรณีและปัญหาที่ถูกสร้างขึ้นเพื่อแก้ไข
Matt Korostoff

5

ผลลัพธ์ของ getElementsByClassName เป็นอาร์เรย์หรือไม่

ไม่

ถ้าไม่มันคืออะไร

เช่นเดียวกับวิธี DOM ทั้งหมดที่ส่งคืนองค์ประกอบหลายรายการเป็น NodeList โปรดดูที่https://developer.mozilla.org/en/DOM/document.getElementsByClassName


3

ตามที่ได้กล่าวไปแล้วgetElementsByClassNameจะส่งคืนHTMLCollectionซึ่งถูกกำหนดเป็น

[Exposed=Window]
interface HTMLCollection {
  readonly attribute unsigned long length;
  getter Element? item(unsigned long index);
  getter Element? namedItem(DOMString name);
};

ก่อนหน้านี้บางเบราว์เซอร์ส่งคืนNodeListแทน

[Exposed=Window]
interface NodeList {
  getter Node? item(unsigned long index);
  readonly attribute unsigned long length;
  iterable<Node>;
};

ความแตกต่างนั้นสำคัญเนื่องจาก DOM4 กำหนดNodeListเป็น iterable

ตามร่างWeb IDL

วัตถุที่ใช้อินเทอร์เฟซที่ได้รับการประกาศว่าสนับสนุนซ้ำแล้วซ้ำอีกเพื่อรับลำดับของค่า

หมายเหตุ : ในภาษา ECMAScript ผูกพันอินเตอร์เฟซที่เป็น iterable จะมี“รายการ”,“forEach”,“กุญแจ”,“ค่า” และ @@ iteratorคุณสมบัติในการของวัตถุต้นแบบอินเตอร์เฟซ

นั่นหมายความว่าถ้าคุณต้องการที่จะใช้งานforEachคุณสามารถใช้วิธีการ DOM ที่ส่งกลับNodeListquerySelectorAllเช่น

document.querySelectorAll(".myclass").forEach(function(element, index, array) {
  // do stuff
});

หมายเหตุยังไม่รองรับอย่างกว้างขวาง โปรดดูวิธีการสำหรับแต่ละโหนดของ Node.childNodes


1
Chrome 49 returnforEach in not a function
Vitaly Zdanevich

@VitalyZdanevich ลองใช้ Chromium 50
Oriol

ใน Chrome 50 ฉันได้รับdocument.querySelectorAll(...).forEach is not a function
Vitaly Zdanevich

@VitalyZdanevich ใช้งานได้กับ Chromium 50 และยังคงใช้งานได้กับ Chromium 53 บางทีมันอาจจะไม่ถือว่าเสถียรพอที่จะส่งไปยัง Chrome 50
Oriol


1

นี่เป็นวิธีที่ปลอดภัยกว่า:

var elements = document.getElementsByClassName("myclass");
for (var i = 0; i < elements.length; i++) myFunction(elements[i]);

0

getElementsByClassNameส่งคืนHTMLCollectionในเบราว์เซอร์สมัยใหม่

ซึ่งเป็น วัตถุคล้ายอาเรย์คล้ายกับข้อโต้แย้งซึ่งfor...ofวนซ้ำโดยดูด้านล่างสิ่งที่MDN doc พูดเกี่ยวกับมัน:

สำหรับ ... ของคำสั่งสร้าง iterating ห่วงมากกว่า iterable วัตถุรวมไปถึง: ในตัวสตริงอาร์เรย์อาร์เรย์เหมือนวัตถุ (เช่นข้อโต้แย้ง หรือ NodeList) TypedArray, แผนที่, ชุดและ iterables ที่ผู้ใช้กำหนด มันเรียกขอซ้ำที่กำหนดเองด้วยคำสั่งที่จะดำเนินการสำหรับมูลค่าของแต่ละคุณสมบัติที่แตกต่างของวัตถุ

ตัวอย่าง

for (let element of getElementsByClassName("classname")){
   element.style.display="none";
}

ไม่เช่นนั้นตามที่ระบุไว้:error TS2488: Type 'HTMLCollectionOf<Element>' must have a '[Symbol.iterator]()' method that returns an iterator.
เต่าน่ารัก

@TurtlesAreCute นี่คือ OP ใช้ javascript ไม่ใช่ typescript และฉันได้ตอบตามคำแนะนำของ vanilla js ดังนั้นใน typescript อาจเป็นวิธีการแก้ปัญหาที่แตกต่างกัน
Haritsinh Gohil

@TurtlesAreCute โดยวิธีมันยังทำงานใน typescript ด้วย แต่คุณต้องพูดถึงชนิดของตัวแปรที่ถูกต้องซึ่งมีองค์ประกอบของคลาส css โดยเฉพาะเพื่อที่จะสามารถโยนมันตามเพื่อดูรายละเอียดคำตอบนี้
Haritsinh Gohil

0

นี่คือการทดสอบที่ฉันสร้างขึ้นใน jsperf: https://jsperf.com/vanillajs-loop-through-elements-of-class

รุ่นสมบูรณ์ที่สุดใน Chrome และ Firefox เป็นรุ่นเก่าสำหรับการวนซ้ำรวมกับ document.getElementsByClassName:

var elements = document.getElementsByClassName('testClass'), elLength = elements.length;
for (var i = 0; i < elLength; i++) {
    elements.item(i).textContent = 'Tested';
};

ใน Safari ตัวแปรนี้เป็นผู้ชนะ:

var elements = document.querySelectorAll('.testClass');
elements.forEach((element) => {
    element.textContent = 'Tested';
});

หากคุณต้องการตัวแปรที่หลากหลายที่สุดสำหรับเบราว์เซอร์ทั้งหมดมันอาจจะเป็นตัวแปรนี้:

var elements = document.getElementsByClassName('testClass');
Array.from(elements).map(
    (element) => {
        return element.textContent = 'Tested';
    }
);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.