For循环用于HTMLCollection元素

我试图在HTMLCollectionOf中设置所有元素的获取id。我写了下面的代码:

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

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

event1
undefined

这和我预料的不一样为什么第二个控制台输出是undefined,而第一个控制台输出是event1?

616141 次浏览

你想把它改成

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
}

对于最初的问题,您错误地使用了for/in。在代码中,key是索引。因此,要从伪数组中获取值,你必须执行list[key],而要获得id,你必须执行list[key].id。但是,您首先不应该对for/in这样做。

摘要(2018年12月新增)

永远不要使用for/in来迭代一个节点列表或HTMLCollection。避免使用的原因如下所述。

所有最新版本的现代浏览器(Safari, Firefox, Chrome, Edge)都支持在DOM列表上进行for/of迭代,例如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用于迭代对象的属性。这意味着它将返回对象的所有可迭代属性。虽然它似乎适用于数组(返回数组元素或伪数组元素),但它也可以返回对象的其他属性,这些属性不是您期望从类数组元素中得到的。而且,你猜怎么着,HTMLCollectionnodeList对象都可以拥有其他属性,这些属性将通过for/in迭代返回。我刚刚在Chrome中尝试了这一点,并以你迭代的方式迭代它将检索列表中的项目(索引0,1,2等…),但也将检索lengthitem属性。for/in迭代根本不适用于HTMLCollection。


请参阅http://jsfiddle.net/jfriend00/FzZ2H/,了解为什么不能使用for/in迭代HTMLCollection。

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

0
1
2
item
namedItem
@@iterator
length

希望现在你能明白为什么要使用for (var i = 0; i < list.length; i++),这样你就可以在迭代中得到012


浏览器对NodeList和HTMLCollection迭代支持的演变

以下是浏览器在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 for/of结构,使用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月,Chrome v54和Firefox v50已经内置了对Symbol.iterator的支持,因此下面的代码可以自行工作。Edge还没有内置。

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

工作演示(在Chrome和Firefox): http://jsfiddle.net/jfriend00/3ddpz8sp/

2017年12月ES6的第三次更新

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

我还更新了上面的jsFiddle,以便它分别测试HTMLCollectionnodeList,并在jsFiddle本身中捕获输出。

2018年3月ES6第四次更新

根据mesqueeeb, Safari也内置了对Symbol.iterator的支持,所以你可以用for (let item of list)代替document.getElementsByClassName()document.querySelectorAll()

第五次更新ES6在2018年4月

显然,Edge 18将在2018年秋季支持用for/of迭代HTMLCollection

2018年11月ES6第六次更新

我可以确认,在Microsoft Edge v18(包括在2018年秋季Windows更新中),你现在可以在Edge中迭代一个HTMLCollection和一个NodeList。

所以,现在所有的现代浏览器都包含对HTMLCollection和NodeList对象的for/of迭代的原生支持。

你不能在NodeLists或HTMLCollections上使用for/in。然而,你可以使用一些Array.prototype方法,只要你.call()他们和传递在NodeListHTMLCollection作为this

因此,可以考虑以下选项作为jfriend00的for循环的替代方案:

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

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

< p >[…传递一个宿主对象(如NodeList)作为 this到本机方法(如forEach)不能保证工作

.所有的浏览器和已知的失败在一些

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

最终你将能够使用ES6 # EYZ0 / # EYZ1!

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

最近版本的Chrome和Firefox已经支持它了。

截至2016年3月,在Chrome 49.0中,for...of适用于HTMLCollection:

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


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

看到# EYZ0。

但是只有当你使用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. prototype]。iterator] = Array.prototype[Symbol.iterator];使用for…of迭代HTMLCollection

在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
});

你可以添加这两行:

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

HTMLCollectiongetElementsByClassNamegetElementsByTagName返回

节点列表返回querySelectorAll

像这样你可以做一个forEach:

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


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


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

我在即11Firefox 49中使用forEach有一个问题

我找到了一个这样的变通办法

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

在边缘

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

如果你在IE9或更高版本上,没有理由使用es6特性来逃避for循环。

在ES5中,有两个不错的选项。首先,你可以“借用”ArrayforEach作为埃文提到

但更好的是……

使用Object.keys()forEach和过滤器自动“拥有属性”。

也就是说,Object.keys本质上相当于用HasOwnProperty执行for... in,但要流畅得多。

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

替代Array.from的方法是使用Array.prototype.forEach.call

< >强forEach: # EYZ0 < / p >

# EYZ1 # EYZ0

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

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


我经常用的简单方法

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()

你也可以这样做:

 let elements = document.getElementsByClassName("classname");
for(let index in  elements) {
if(index <= elements.length) {
console.log(elements[index]);
}
}

let elements = document.getElementsByClassName("classname");
for (let index in elements) {
if (index <= elements.length) {
console.log(elements[index]);
}
}
<div class="classname"> element 1 </div>
<div class="classname"> element 2 </div>
<div class="classname"> element 3 </div>

 let elements = document.getElementsByClassName("classname");
for(let ele of  elements) {
console.log(ele);
}

let elements = document.getElementsByClassName("classname");
for (let ele of elements) {
console.log(ele);
}
<div class="classname"> element 1 </div>
<div class="classname"> element 2 </div>
<div class="classname"> element 3 </div>
<div class="classname"> element 4 </div>