【问题标题】:For loop for HTMLCollection elementsHTMLCollection 元素的 for 循环
【发布时间】:2014-05-10 08:43:34
【问题描述】:

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

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

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

event1
undefined

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

【问题讨论】:

  • 注意事项:无论您选择何种结构,请注意getElementsByClassName 提供了具有该 CSS 类的节点的实时集合。因此,如果您使用循环内迭代的节点的 class 属性,该集合可能会发生变化。在这种情况下,除了Array.from(....).forEach 之外,大多数构造都会被折腾。 Array.from 进行对象克隆并创建一个单独的对象,然后对其进行迭代。

标签: javascript dom


【解决方案1】:

在回答原始问题时,您错误地使用了for/in。在您的代码中,key 是索引。因此,要从伪数组中获取值,您必须执行 list[key] 并获取 id,您必须执行 list[key].id。但是,您首先不应该使用 for/in 来执行此操作。

摘要(2018 年 12 月添加)

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

所有最新版本的现代浏览器(Safari、Firefox、Chrome、Edge)都支持 for/of 在 DOM 列表上迭代,例如 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 等...),但也会检索 length 和 item 属性。 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 &lt; list.length; i++),这样您就可以在迭代中获得0、1 和2。


浏览器支持 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 结构与 NodeList 和 HTMLCollection 添加到您的代码中:

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/。


2016 年 12 月 ES6 的第二次更新

截至 2016 年 12 月,Symbol.iterator 支持已内置于 Chrome 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/

2017 年 12 月 ES6 的第三次更新

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

我还更新了上面的 jsFiddle,因此它分别测试 HTMLCollection 和 nodeList 并在 jsFiddle 本身中捕获输出。

2018 年 3 月 ES6 的第四次更新

对于 mesqueeeb,Safari 也内置了Symbol.iterator 支持,因此您可以将for (let item of list) 用于document.getElementsByClassName() 或document.querySelectorAll()。

2018 年 4 月 ES6 的第五次更新

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

2018 年 11 月 ES6 的第六次更新

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

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

【讨论】:

  • 感谢您所做的出色更新。只是出于兴趣,你知道他们是否会在规范中添加 HTMLCollection 应该有 Symbol.iterator 吗?我知道所有的浏览器都在这样做,但据我所知,规范没有提及,Typescript 定义是根据规范构建的,而不是实现。
  • @WORMSS - 我不知道。但是,如果我猜的话,LivingStandard document: HTMLCollection 中的这条注释是我们无法摆脱网络的历史文物。虽然当然欢迎开发人员继续使用它,但新的 API 标准设计人员不应该使用它(在 IDL 中使用 sequence 代替) 听起来它不太可能再得到增强,因为他们不想要鼓励您继续使用实时集合 API - 可能是因为如果您在修改 DOM 时迭代实时集合,很容易产生错误。
【解决方案2】:

您不能在NodeLists 或HTMLCollections 上使用for/in。但是,您可以使用一些Array.prototype 方法,只要您.call() 它们并将NodeList 或HTMLCollection 作为this 传递。

所以考虑以下作为jfriend00's for loop 的替代方案:

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

有一个很好的article on MDN 涵盖了这种技术。请注意他们关于浏览器兼容性的警告:

[...] 将主机对象(如NodeList)作为 this 到本机方法(例如forEach)不能保证在其中工作 所有浏览器,并且已知在某些浏览器中失败。

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

更新(2014 年 8 月 30 日):最终您将能够使用 ES6 for/of!

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

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

【讨论】:

  • 非常好!我使用这种技术从&lt;select multiple&gt; 中获取选定选项的值。示例:[].map.call(multiSelect.selectedOptions, function(option) { return option.value; })
  • 我正在寻找 ES2015 解决方案,因此感谢您确认 for ... of 有效。
【解决方案3】:

在 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 猜猜我所做的是,浅层克隆一个类似数组的结构
  • 我明白了,谢谢——现在我找到了我正在寻找的文档:developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
  • 我一直用这个,比 Array.from 更容易看,我只是想知道它是否有相当大的性能或内存缺陷。例如,如果我需要迭代表格行的单元格,我使用 [...row.cells].forEach 而不是 row.querySelectorAll('td')
【解决方案4】:

你可以添加这两行:

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

HTMLCollection 由 getElementsByClassName 和 getElementsByTagName

返回

NodeList 由 querySelectorAll

返回

像这样你可以做一个forEach:

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

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

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

【讨论】:

  • 这个答案似乎很有效。有什么收获?
  • 问题是这个解决方案在 IE11 上不起作用!不过很好的解决方案。
  • 我很好奇浏览器对此的支持。看起来棒极了。
【解决方案5】:

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

forEach: Array.prototype.forEach.call(htmlCollection, i =&gt; { console.log(i) });

地图: Array.prototype.map.call(htmlCollection, i =&gt; { console.log(i) });

等等……

【讨论】:

    【解决方案6】:

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

    在 ES5 中,有两个不错的选择。首先,你可以将Array的forEach“借用”为evan mentions。

    但更好...

    使用Object.keys(),确实拥有forEach,并自动过滤到“拥有属性”。

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

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

    【讨论】:

      【解决方案7】:

      我在 IE 11 和 Firefox 49

      中使用 forEach 时遇到问题

      我找到了这样的解决方法

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

      【讨论】:

      【解决方案8】:

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

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

      见here the documentation。

      但只有在您使用for...of 应用以​​下解决方法之前 时它才有效:

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

      for...of 和 NodeList 的使用也是如此:

      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];用 for...of

      遍历 HTMLCollection

      【讨论】:

      • 此问题已于 2016 年 4 月修复。您无需添加 HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; 即可使用 for...of 迭代 HTMLCollection。
      【解决方案9】:

      在边缘

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

      【讨论】:

        【解决方案10】:

        我经常使用的简单解决方法

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

        【讨论】:

          【解决方案11】:

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

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

          【讨论】:

            【解决方案12】:

            你也可以这样做:

             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>

            【讨论】:

              【解决方案13】:

              你想把它改成

              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 (key in list) 将返回 HTMLCollection 的几个属性,这些属性不应该是集合中的项目。
              猜你喜欢
              • 1970-01-01
              • 2017-02-08
              • 1970-01-01
              • 1970-01-01
              • 2018-03-17
              • 2016-01-26
              • 1970-01-01
              相关资源
              最近更新 更多