JS: lặp lại kết quả của getElementsByClassName bằng Array.forEach


240

Tôi muốn lặp lại một số thành phần DOM, tôi đang làm điều này:

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

nhưng tôi gặp lỗi:

document.getElementsByClassName ("mygroup"). forEach không phải là một hàm

Tôi đang sử dụng Firefox 3 vì vậy tôi biết rằng cả hai getElementsByClassNameArray.forEachđều có mặt. Điều này hoạt động tốt:

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

Là kết quả của getElementsByClassNamemột mảng? Nếu không, nó là gì?

Câu trả lời:


384

Không. Như được chỉ định trong DOM4 , HTMLCollectionít nhất là trong các trình duyệt hiện đại. Các trình duyệt cũ hơn đã trả về a NodeList).

Trong tất cả các trình duyệt hiện đại (gần như mọi thứ khác IE <= 8), bạn có thể gọi forEachphương thức của Array , chuyển cho nó danh sách các phần tử (có thể là HTMLCollectionhoặc NodeList) làm thisgiá trị:

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

Nếu bạn đang ở vị trí hạnh phúc khi có thể sử dụng ES6 (tức là bạn có thể bỏ qua Internet Explorer một cách an toàn hoặc bạn đang sử dụng bộ chuyển đổi ES5), bạn có thể sử dụng Array.from:

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

29
Không cần phải chuyển đổi nó thành một mảng đầu tiên. Chỉ cần sử dụng [].forEach.call(elsArray, function () {...}).
kay - SE là ác

1
Nó KHÔNG phải là một NodeList. Đó là một đối tượng giống như mảng. Tôi thậm chí không nghĩ rằng nó có một loại thể hiện. querySelectorAllphương thức trả về một NodeList mặc dù.
Maksim Vi.

2
@MaksimVi. Bạn hoàn toàn đúng: DOM4 chỉ định rằng document.getElementsByClassName()sẽ trả về một HTMLCollection(rất giống nhưng không phải là NodeList). Cảm ơn đã chỉ ra sai lầm.
Tim Down

@MaksimVi.: Tôi tự hỏi liệu điều đó có thay đổi vào một lúc nào đó không. Tôi thường kiểm tra những thứ này.
Tim Down

1
@TimDown, Cảm ơn vì tiền HTMLCollectionboa. Bây giờ tôi cuối cùng có thể sử dụng HTMLCollection.prototype.forEach = Array.prototype.forEach;trong mã của tôi.
Maksim Vi.

70

Bạn có thể sử dụng Array.fromđể chuyển đổi bộ sưu tập thành mảng, cách này sạch hơn Array.prototype.forEach.call:

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

Trong các trình duyệt cũ không hỗ trợ Array.from, bạn cần sử dụng cái gì đó như Babel.


ES6 cũng thêm cú pháp này:

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

Phần còn lại phá hủy với ...các công việc trên tất cả các đối tượng giống như mảng, không chỉ bản thân mảng, sau đó cú pháp mảng cũ tốt được sử dụng để xây dựng một mảng từ các giá trị.


Mặc dù hàm thay thế querySelectorAll(loại nào làm cho getElementsByClassNamelỗi thời) trả về một bộ sưu tập vốn có forEachnguyên bản, các phương thức khác như maphoặc filterbị thiếu, vì vậy cú pháp này vẫn hữu ích:

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

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

6
Lưu ý: không có dịch chuyển như đề xuất (Babel), điều này KHÔNG tương thích trong IE <Edge, Opera, Safari <9, trình duyệt Android, Chrome cho Android, ... vv) Nguồn: mozilla dev docs
Sean

30

Hoặc bạn có thể sử dụng querySelectorAlltrả về NodeList :

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

Được hỗ trợ bởi các trình duyệt hiện đại (bao gồm Edge, nhưng không phải IE):
Tôi có thể sử dụng querySelector ALL
NodeList.prototype.forEach ()

MDN: Document.querySelector ALL ()


4
Hãy nhớ rằng hình phạt hiệu suất trên getEuityByClassName
Szabolcs Páll

3
Hình phạt hiệu suất là không đáng kể so với các nhiệm vụ chuyên sâu khác như sửa đổi DOM. Nếu tôi thực hiện 60.000 trong số này trong 1 mili giây , tôi khá chắc chắn rằng đó sẽ không phải là vấn đề đối với bất kỳ việc sử dụng hợp lý nào :)
icl7126

1
Bạn đã liên kết điểm chuẩn sai. Đây là một số đo chính xác.net/Benchmark/Show / 4076/0 / Chỉ cần chạy nó trên điện thoại cấp thấp của tôi, nhận 160k / s so với 380k / s. Vì bạn đã đề cập đến thao tác DOM, ở đây cũng vậy, đó là quá đoethat.net /Benchmark/Show/5705/0/ Đổi Got 50k / s so với 130k / s. Như bạn thấy việc thao tác DOM thậm chí còn chậm hơn, có thể do NodeList là tĩnh (như được đề cập bởi những người khác). Vẫn có thể phủ nhận trong hầu hết các trường hợp sử dụng, nhưng dù sao cũng chậm hơn gần 3 lần.
Szabolcs Páll

14

Chỉnh sửa: Mặc dù loại trả về đã thay đổi trong các phiên bản HTML mới (xem câu trả lời được cập nhật của Tim Down), mã bên dưới vẫn hoạt động.

Như những người khác đã nói, đó là một NodeList. Đây là một ví dụ đầy đủ, hoạt động mà bạn có thể thử:

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

Điều này hoạt động trong IE 9, FF 5, Safari 5 và Chrome 12 trên Win 7.


9

Kết quả của getElementsByClassName()không phải là một mảng, mà là một đối tượng giống như mảng . Cụ thể, nó được gọi là an HTMLCollection, không bị nhầm lẫn NodeList( forEach()phương thức riêng ).

Một cách đơn giản với ES2015 để chuyển đổi một đối tượng giống như mảng để sử dụng Array.prototype.forEach()mà chưa được đề cập là sử dụng toán tử trải hoặc cú pháp trải rộng :

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

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

2
Tôi cảm thấy đây thực sự là cách đúng đắn để làm điều đó trong các trình duyệt hiện đại. Đây là cú pháp lây lan trường hợp sử dụng chính xác đã được tạo để giải quyết.
Matt Korostoff


3

Như đã nói, getElementsByClassNametrả về HTMLCollection , được định nghĩa là

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

Trước đây, một số trình duyệt đã trả về một NodeList thay thế.

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

Sự khác biệt rất quan trọng, vì DOM4 hiện định nghĩa các NodeList là iterable.

Theo dự thảo IDL Web ,

Các đối tượng thực hiện một giao diện được khai báo là hỗ trợ lặp được lặp đi lặp lại để có được một chuỗi các giá trị.

Lưu ý : Trong liên kết ngôn ngữ ECMAScript, một giao diện có thể lặp lại sẽ có các mục nhập, một phần mềm, các khóa, một, các giá trị, các giá trị, các giá trị, các giá trị và các thuộc tính trên giao diện của nó .

Điều đó có nghĩa là, nếu bạn muốn sử dụng forEach, bạn có thể sử dụng phương thức DOM trả về NodeList , như thế nào querySelectorAll.

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

Lưu ý điều này chưa được hỗ trợ rộng rãi. Cũng xem phương pháp forEach của Node.childNodes?


1
Trả lại Chrome 49forEach in not a function
Vitaly Zdanevich

@VitalyZdanevich Hãy thử Chromium 50
Oriol

Trên Chrome 50 tôi đang nhận đượcdocument.querySelectorAll(...).forEach is not a function
Vitaly Zdanevich

@VitalyZdanevich Nó hoạt động trên Chromium 50 và vẫn hoạt động trên Chromium 53. Có lẽ nó không được coi là đủ ổn định để được chuyển đến Chrome 50.
Oriol


1

Đây là cách an toàn hơn:

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

0

getElementsByClassNametrả về HTMLCollection trong các trình duyệt hiện đại.

đó là đối tượng giống như mảng tương tự như các đối số có thể for...oflặp theo vòng lặp, xem bên dưới tài liệu MDN đang nói gì về nó:

Câu lệnh for ... tạo ra một vòng lặp lặp trên các đối tượng có thể lặp , bao gồm: String, Array, các đối tượng giống như Array (ví dụ: đối số hoặc NodeList), TypedArray, Map, Set và iterables do người dùng định nghĩa. Nó gọi một móc lặp tùy chỉnh với các câu lệnh được thực thi cho giá trị của từng thuộc tính riêng biệt của đối tượng.

thí dụ

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

Không phải vậy, theo Bản mô tả:error TS2488: Type 'HTMLCollectionOf<Element>' must have a '[Symbol.iterator]()' method that returns an iterator.
Rùa dễ thương

@TurtlesAreCute, Ở đây OP đang sử dụng javascript không phải là typcript và tôi đã trả lời theo khuyến nghị của vanilla js vì vậy trong bản in có thể là giải pháp khác nhau cho vấn đề.
Haritsinh Gohil

@TurtlesAreCute, Nhân tiện, nó cũng hoạt động trong bản thảo, nhưng bạn phải đề cập đến đúng loại biến chứa phần tử của lớp css cụ thể, để nó có thể sử dụng nó cho phù hợp, để biết chi tiết câu trả lời này .
Haritsinh Gohil

0

Đây là một bài kiểm tra tôi đã tạo trên jsperf: https://jsperf.com/vanillajs-loop-ENC-elements-of- class

Phiên bản hoàn hảo nhất trong Chrome và Firefox là phiên bản cũ tốt cho vòng lặp kết hợp với document.getElementsByClassName:

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

Trong Safari, biến thể này là người chiến thắng:

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

Nếu bạn muốn biến thể hoàn hảo nhất cho tất cả các trình duyệt thì đó có thể là biến thể này:

var elements = document.getElementsByClassName('testClass');
Array.from(elements).map(
    (element) => {
        return element.textContent = 'Tested';
    }
);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.