HTMLCollection元素的For循环


405

我试图设置获取所有元素的ID HTMLCollectionOf。我写了以下代码:

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

但是我在控制台中得到以下输出:

event1
undefined

这不是我所期望的。为什么第二个控制台输出undefined却第一个控制台输出是event1


23
为什么有关for ... in语句的标题为何标题为“ foreach”?我在谷歌搜索时偶然来到了这里。
mxt3

@ mxt3以我的理解,这是Java for-each循环的一种类似方式(工作原理相同)。

1
@ mxt3我也这么想!但是,在阅读了可接受的答案之后,我发现使用Array.from()解决了我的foreach问题的这一行:Array.from(document.getElementsByClassName("events")).forEach(function(item) {
HoldOffHunger

Answers:


839

在回答原始问题时,您使用for/in不正确。在您的代码中,key是索引。因此,要从伪数组中获取值,就必须这样做,list[key]而要获取id,就必须这样做list[key].id。但是,首先不应该这样做for/in

摘要(于2018年12月新增)

永远不要使用for/in来迭代nodeList或HTMLCollection。避免这种情况的原因如下所述。

所有最新版本的现代浏览器(Safari,Firefox,Chrome,Edge)都支持for/ofDOM列表(例如nodeList或)上的迭代HTMLCollection

这是一个例子:

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等...),但还将检索lengthitem属性。该for/in迭代根本不会对的HTMLCollection工作。


请参阅http://jsfiddle.net/jfriend00/FzZ2H/了解为什么无法使用来迭代HTMLCollectionfor/in

在Firefox中,您的for/in迭代将返回以下项目(对象的所有可迭代属性):

0
1
2
item
namedItem
@@iterator
length

我们希望,现在你可以看到为什么要使用for (var i = 0; i < list.length; i++)代替你只是得到012在你的迭代。


以下是浏览器在2015-2018年期间的演变方式的演变,为您提供了其他迭代方式。由于您可以使用上述选项,因此现代浏览器现在不需要这些。

2015年ES6更新

ES6中增加了Array.from()将类似数组的结构转换为实际数组的功能。这样就可以像这样直接枚举列表:

"use strict";

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

工作演示(截至2016年4月在Firefox,Chrome和Edge中):https : //jsfiddle.net/jfriend00/8ar4xn2s/


2016年ES6更新

您现在可以使用ES6与构建物/ NodeListHTMLCollection只需添加这对您的代码:

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迭代器同时附加到NodeList和HTMLCollection原型,以便当进行for / of迭代时,它使用Array迭代器对其进行迭代。

工作演示:http : //jsfiddle.net/jfriend00/joy06u4e/


ES6的第二次更新于2016年12月

截至2016年12月,Symbol.iteratorChrome 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年12月)

截至2017年12月,此功能在Edge 41.16299.15.0中适用nodeList于in document.querySelectorAll()而不是HTMLCollectionin in,document.getElementsByClassName()因此您必须手动分配迭代器以在Edge中将其用于HTMLCollection。为什么要修复一种收集类型而不解决另一种收集类型,这完全是个谜。但是,现在至少可以在当前版本的Edge中使用document.querySelectorAll()带有ES6 for/of语法的结果。

我还更新了上述的jsfiddle所以它同时测试HTMLCollectionnodeList分开,并捕获的的jsfiddle本身的输出。

ES6的第四次更新于2018年3月

每mesqueeeb,Symbol.iterator支持已经内置Safari浏览器,所以你可以使用for (let item of list)的两种document.getElementsByClassName()document.querySelectorAll()

ES6的第五次更新于2018年4月

显然,对HTMLCollectionwith 进行迭代的支持for/of将在2018年秋季提供给Edge 18。

ES6的第六次更新于2018年11月

我可以确认使用Microsoft Edge v18(包含在2018年秋季的Windows Update中)现在可以在Edge中使用for / of迭代HTMLCollection和NodeList。

因此,现在所有现代浏览器都包含for/of对HTMLCollection和NodeList对象的迭代的本机支持。


1
感谢您对JS进行的非常详细的更新。这可以帮助初学者在仅适用于特定JS版本的其他文章/帖子的背景下理解此建议。
brownmagik352

出色的答案,惊人的更新支持。谢谢。
哥萨克人

79

您不能在s或s 上使用for/ 。但是,你可以使用一些方法,只要它们并传入或作为。inNodeListHTMLCollectionArray.prototype.call()NodeListHTMLCollectionthis

因此,请考虑以下方法作为jfriend00 for循环的替代方法:

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

在MDN上有一篇很好的文章介绍了该技术。请注意他们关于浏览器兼容性的警告:

不能保证将宿主对象(如NodeListthis传递给本地方法(如forEach),不能保证在所有浏览器中都能正常工作,并且已知在某些浏览器中会失败。

因此,尽管这种方法很方便,但for循环可能是与浏览器最兼容的解决方案。

更新(2014年8月30日):最终您将可以使用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工作。
理查德·特纳

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.from容易得多,我只是想知道它是否具有相当大的性能或内存缺陷。例如,如果我需要[...row.cells].forEachrow.querySelectorAll('td')
迭代表

16

您可以添加以下两行:

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

HTMLCollectiongetElementsByClassNamegetElementsByTagName返回

NodeListquerySelectorAll返回

这样,您可以进行forEach:

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

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

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

这个答案似乎很有效。有什么收获?
Peheje

2
值得注意的是,该解决方案在IE11上不起作用!好的解决方案。
拉胡尔·加巴


7

我在IE 11Firefox 49中使用forEach时遇到问题

我发现了这样的解决方法

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

IE11的绝佳解决方案!曾经是一种常见的技术 ...
mb21 '18

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

for如果您使用的是IE9或更高版本,则没有理由使用es6功能来避免循环。

在ES5中,有两个不错的选择。首先,您可以像evan提到ArrayforEach那样“借用”

但是更好...

使用Object.keys(),它确实具有forEach和过滤器以自动“拥有属性”。

也就是说,Object.keys本质上等同于使用for... in进行a HasOwnProperty,但是要平滑得多。

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

5

自2016年3月起,在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...ofNodeList

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

我相信/ for...of没有上述解决方法,很快就会奏效。未解决的问题在这里:

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

更新:请参阅下面的Expenzor注释:截至2016年4月,此问题已得到修复。您无需添加HTMLCollection.prototype [Symbol.iterator] = Array.prototype [Symbol.iterator]; 遍历带有... of的HTMLCollection


4
自2016年4月起,此问题已修复。您无需HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];HTMLCollectionwith上进行迭代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)

之后,您可以在选择项上运行任何所需的Array方法

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

1

如果您使用ES的旧版本(例如ES5),则可以使用as any

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

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
仅供参考,请查看我的答案,以了解为何此操作无法正常工作。在for (key in list)返回的几个属性HTMLCollection这并不意味着是集合中的项目。
jfriend00 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.