Vòng lặp cho các phần tử HTMLCollection


405

Tôi đang cố gắng để có được id của tất cả các yếu tố trong một HTMLCollectionOf. Tôi đã viết đoạn mã sau:

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

Nhưng tôi đã nhận được đầu ra sau trong giao diện điều khiển:

event1
undefined

đó không phải là điều tôi mong đợi Tại sao đầu ra giao diện điều khiển thứ hai undefinednhưng đầu ra giao diện điều khiển đầu tiên là event1?


23
Tại sao tiêu đề lại nói "foreach" khi câu hỏi nói về ... trong tuyên bố? Tôi đến đây một cách tình cờ khi googling.
mxt3

@ mxt3 Vâng, theo cách hiểu của tôi, đó là một bản giải thích cho mỗi vòng lặp trong Java (hoạt động như nhau).

1
@ mxt3 Tôi cũng nghĩ như vậy! Nhưng sau khi đọc qua câu trả lời được chấp nhận, tôi đã tìm thấy dòng này, giải quyết vấn đề foreach của tôi, sử dụng Array.from ():Array.from(document.getElementsByClassName("events")).forEach(function(item) {
Hold OfferHunger

Câu trả lời:


839

Để trả lời cho câu hỏi ban đầu, bạn đang sử dụng for/inkhông chính xác. Trong mã của bạn, keylà chỉ mục. Vì vậy, để có được giá trị từ mảng giả, bạn phải làm list[key]và để lấy id, bạn sẽ làm list[key].id. Nhưng, bạn không nên làm điều này với for/inở nơi đầu tiên.

Tóm tắt (được thêm vào tháng 12 năm 2018)

Đừng bao giờ sử dụng for/inđể lặp lại một danh sách nút hoặc HTMLCollection. Những lý do để tránh nó được mô tả dưới đây.

Tất cả các phiên bản gần đây của trình duyệt hiện đại (Safari, Firefox, Chrome, Edge) đều hỗ trợ for/oflặp trên danh sách DOM như vậy nodeListhoặc HTMLCollection.

Đây là một ví dụ:

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

Để bao gồm các trình duyệt cũ hơn (bao gồm cả những thứ như IE), điều này sẽ hoạt động ở mọi nơi:

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

Giải thích cho lý do tại sao bạn không nên sử dụng for/in

for/incó nghĩa là để lặp lại các thuộc tính của một đối tượng. Điều đó có nghĩa là nó sẽ trả về tất cả các thuộc tính lặp lại của một đối tượng. Mặc dù nó có vẻ hoạt động cho một mảng (trả về các phần tử mảng hoặc phần tử giả), nó cũng có thể trả về các thuộc tính khác của đối tượng không phải là những gì bạn đang mong đợi từ các phần tử giống như mảng. Và, đoán xem, một HTMLCollectionhoặc một nodeListđối tượng có thể có các thuộc tính khác sẽ được trả về bằng một for/inlần lặp. Tôi vừa thử điều này trong Chrome và lặp lại theo cách bạn đang lặp lại, nó sẽ lấy các mục trong danh sách (chỉ mục 0, 1, 2, v.v ...), nhưng cũng sẽ truy xuất các thuộc tính lengthitem. Các for/inlặp chỉ đơn giản là sẽ không làm việc cho một HTMLCollection.


Xem http://jsfiddle.net/jfriend00/FzZ2H/ để biết lý do tại sao bạn không thể lặp lại HTMLCollection với for/in.

Trong Firefox, for/inphép lặp của bạn sẽ trả về các mục này (tất cả các thuộc tính lặp của đối tượng):

0
1
2
item
namedItem
@@iterator
length

Hy vọng rằng, bây giờ bạn có thể thấy lý do tại sao bạn muốn sử dụng for (var i = 0; i < list.length; i++)thay vì vậy bạn chỉ có được 0, 12trong lần lặp của bạn.


Sau đây là một sự phát triển về cách các trình duyệt đã phát triển trong khoảng thời gian 2015-2018 cung cấp cho bạn các cách bổ sung để lặp lại. Hiện tại không cần điều này trong các trình duyệt hiện đại vì bạn có thể sử dụng các tùy chọn được mô tả ở trên.

Cập nhật cho ES6 năm 2015

Thêm vào ES6 là Array.from()nó sẽ chuyển đổi một cấu trúc giống như mảng thành một mảng thực tế. Điều đó cho phép người ta liệt kê một danh sách trực tiếp như thế này:

"use strict";

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

Bản demo hoạt động (trong Firefox, Chrome và Edge kể từ tháng 4 năm 2016): https://jsfiddle.net/jfriend00/8ar4xn2s/


Cập nhật cho ES6 năm 2016

Bây giờ bạn có thể sử dụng ES6 cho / của cấu trúc với a NodeListvà an HTMLCollectionbằng cách thêm mã này vào mã của bạn:

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

Sau đó, bạn có thể làm:

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

Điều này hoạt động trong phiên bản hiện tại của Chrome, Firefox và Edge. Điều này hoạt động vì nó gắn Array iterator vào cả hai nguyên mẫu NodeList và HTMLCollection để khi / cho lặp lại chúng, nó sử dụng iterator Array để lặp lại chúng.

Bản demo hoạt động: http://jsfiddle.net/jfriend00/joy06u4e/ .


Cập nhật lần thứ hai cho ES6 vào tháng 12 năm 2016

Kể từ tháng 12 năm 2016, Symbol.iteratorhỗ trợ đã được tích hợp sẵn cho Chrome v54 và Firefox v50 để mã bên dưới hoạt động. Nó chưa được tích hợp sẵn cho Edge.

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

Bản trình diễn hoạt động (trong Chrome và Firefox): http://jsfiddle.net/jfriend00/3ddpz8sp/

Cập nhật lần thứ ba cho ES6 vào tháng 12 năm 2017

Tính đến tháng 12 năm 2017, khả năng này hoạt động trong cạnh 41.16299.15.0 cho một nodeListnhư trong document.querySelectorAll(), nhưng không phải là một HTMLCollectionnhư trong document.getElementsByClassName()vì vậy bạn phải tự gán các iterator để sử dụng nó trong Edge cho một HTMLCollection. Đó là một bí ẩn hoàn toàn tại sao họ sửa một loại bộ sưu tập, nhưng không phải loại khác. Nhưng, ít nhất bạn có thể sử dụng kết quả document.querySelectorAll()với for/ofcú pháp ES6 trong các phiên bản hiện tại của Edge.

Tôi cũng đã cập nhật jsFiddle ở trên để nó kiểm tra cả hai HTMLCollectionnodeListriêng biệt và nắm bắt đầu ra trong chính jsFiddle.

Cập nhật lần thứ tư cho ES6 vào tháng 3 năm 2018

Trên mỗi mesqueeeb, Symbol.iteratorhỗ trợ cũng đã được tích hợp vào Safari, vì vậy bạn có thể sử dụng for (let item of list)cho một trong hai document.getElementsByClassName()hoặc document.querySelectorAll().

Cập nhật lần thứ năm cho ES6 vào tháng 4 năm 2018

Rõ ràng, hỗ trợ cho việc lặp lại HTMLCollectionvới for/ofsẽ đến với Edge 18 vào mùa thu 2018.

Cập nhật lần thứ sáu cho ES6 vào tháng 11 năm 2018

Tôi có thể xác nhận rằng với Microsoft Edge v18 (được bao gồm trong Windows Update mùa thu 2018), giờ đây bạn có thể lặp lại cả HTMLCollection và NodeList với for / of trong Edge.

Vì vậy, bây giờ tất cả các trình duyệt hiện đại đều có hỗ trợ riêng cho việc for/oflặp lại cả hai đối tượng HTMLCollection và NodeList.


1
Cảm ơn bạn đã cập nhật rất chi tiết vì JS đã cập nhật. Điều này giúp người mới bắt đầu hiểu lời khuyên này trong ngữ cảnh của các bài viết / bài đăng khác chỉ áp dụng cho một bản phát hành JS cụ thể.
brownmagik352

Câu trả lời nổi bật, hỗ trợ cập nhật tuyệt vời .. Cảm ơn bạn.
cossacksman

79

Bạn không thể sử dụng for/ intrên NodeLists hoặc HTMLCollections. Tuy nhiên, bạn có thể sử dụng một số Array.prototypephương pháp, miễn là bạn .call()và truyền vào NodeListhoặc HTMLCollectionnhư this.

Vì vậy, hãy xem xét những điều sau đây thay thế cho vòng lặp của jfriend00for :

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

Có một bài viết hay về MDN bao gồm kỹ thuật này. Lưu ý cảnh báo của họ về khả năng tương thích trình duyệt:

[...] việc truyền một đối tượng máy chủ (như a NodeList) như thismột phương thức gốc (chẳng hạn như forEach) không được đảm bảo để hoạt động trong tất cả các trình duyệt và được biết là đã thất bại ở một số trình duyệt.

Vì vậy, trong khi phương pháp này thuận tiện, một forvòng lặp có thể là giải pháp tương thích với trình duyệt nhất.

Cập nhật (ngày 30 tháng 8 năm 2014): Cuối cùng, bạn sẽ có thể sử dụng ES6 for/of !

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

Nó đã được hỗ trợ trong các phiên bản gần đây của Chrome và Firefox.


1
Rất đẹp! Tôi đã sử dụng kỹ thuật này để lấy các giá trị của các tùy chọn đã chọn từ a <select multiple>. Ví dụ:[].map.call(multiSelect.selectedOptions, function(option) { return option.value; })
XåpplI'-I0llwlg'I -

1
Tôi đã tìm kiếm một giải pháp ES2015 cho vấn đề này, vì vậy cảm ơn bạn đã xác nhận rằng nó for ... ofhoạt động.
Richard Turner

60

Trong ES6, bạn có thể làm một cái gì đó như [...collection], hoặc Array.from(collection),

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

Ví dụ:-

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

@DanielM đoán những gì tôi đã làm là, nông nhân bản một cấu trúc giống như mảng
mido

Tôi hiểu rồi, cảm ơn - bây giờ tôi đã tìm thấy tài liệu mà tôi đang tìm kiếm: developer.mozilla.org/en/docs/Web/JavaScript/Reference/
trộm

Tôi luôn sử dụng cái này, dễ nhìn hơn nhiều so với Array.from, tôi chỉ tự hỏi liệu nó có hiệu suất đáng kể hay hạn chế bộ nhớ. Ví dụ: nếu tôi cần lặp lại các ô của hàng trong bảng, tôi sử dụng [...row.cells].forEachthay vì thực hiệnrow.querySelectorAll('td')
Mojimi

16

bạn có thể thêm hai dòng này:

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

HTMLCollection được trả về bởi getElementsByClassNamegetElementsByTagName

NodeList được trả về bởi querySelector ALL

Như thế này bạn có thể làm một forEach:

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

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

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

Câu trả lời này có vẻ rất hiệu quả. Nắm bắt được những gì?
Peheje

2
Điều hấp dẫn là giải pháp này không hoạt động trên IE11! Giải pháp tốt mặc dù.
Rahul Gaba


7

Tôi gặp sự cố khi sử dụng forEach trong IE 11Firefox 49

Tôi đã tìm thấy một cách giải quyết như thế này

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

Giải pháp tuyệt vời cho IE11! Được sử dụng là một kỹ thuật phổ biến ...
mb21 17/07/18

6

Thay thế cho Array.fromlà sử dụngArray.prototype.forEach.call

cho mỗi: Array.prototype.forEach.call(htmlCollection, i => { console.log(i) });

bản đồ: Array.prototype.map.call(htmlCollection, i => { console.log(i) });

Vân vân...


6

Không có lý do để sử dụng các tính năng es6 để thoát khỏi forvòng lặp nếu bạn đang ở trên IE9 trở lên.

Trong ES5, có hai lựa chọn tốt. Thứ nhất, bạn có thể "mượn" ArrayforEachnhư evan đề cập đến .

Nhưng thậm chí còn tốt hơn ...

Sử dụng Object.keys(), mà khôngforEachvà bộ lọc để "tài sản riêng" tự động.

Đó là, Object.keysvề cơ bản tương đương với việc làm for... invới a HasOwnProperty, nhưng mượt mà hơn nhiều.

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

5

Kể từ tháng 3 năm 2016, trong Chrome 49.0, for...ofhoạt động cho HTMLCollection:

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

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

Xem tài liệu ở đây .

Nhưng nó chỉ hoạt động nếu bạn áp dụng cách giải quyết sau đây trước khi sử dụng for...of:

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

Điều tương tự là cần thiết để sử dụng for...ofvới NodeList:

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

Tôi tin / hy vọng for...ofsẽ sớm làm việc mà không có cách giải quyết trên. Vấn đề mở ở đây:

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

Cập nhật: Xem bình luận của Expenzor bên dưới: Điều này đã được sửa vào tháng 4 năm 2016. Bạn không cần thêm HTMLCollection.prototype [Symbol.iterator] = Array.prototype [Symbol.iterator]; lặp đi lặp lại qua HTMLCollection với ... của


4
Điều này đã được khắc phục như tháng Tư năm 2016. Bạn không cần phải thêm HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];để lặp qua một HTMLCollectionvới for...of.
Expenzor

3

Trên mép

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

Cách giải quyết dễ dàng mà tôi luôn sử dụng

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

Sau này, bạn có thể chạy bất kỳ phương thức Array mong muốn nào trên vùng chọn

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

1

nếu bạn sử dụng các lọ đựng oldder của ES, (ví dụ ES5), bạn có thể sử dụng as any:

for (let element of elementsToIterate as any) {
      console.log(element);
}

0

Bạn muốn thay đổi nó thành

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, xem câu trả lời của tôi tại sao điều này sẽ không hoạt động đúng. Các for (key in list)sẽ trở lại một vài thuộc tính của HTMLCollectionđiều đó không có nghĩa là các mặt hàng trong bộ sưu tập.
jfriend00
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.