使用Array.forEach迭代getElementsByClassName的结果

417

我想迭代一些DOM元素,我正在这样做:

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

但是我遇到了一个错误:

document.getElementsByClassName("myclass").forEach 不是一个函数

我正在使用 Firefox 3,所以我知道 getElementsByClassNameArray.forEach 都存在。下面的代码可以正常工作:

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

getElementsByClassName的结果是否为数组?如果不是,那它是什么?

14个回答

615

不,这不是一个数组。如DOM4中指定,它是一个HTMLCollection(至少在现代浏览器中是这样的。旧版浏览器返回NodeList)。

在所有现代浏览器中(几乎所有IE版本<=8除外),您可以调用Array的forEach方法,将元素列表(无论是HTMLCollection还是NodeList)作为this值传递:

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

如果您有幸能够使用ES6(即您可以安全地忽略Internet Explorer或者您正在使用ES5转换器),您可以使用Array.from

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

39
无需先将其转换为数组。只需使用 [].forEach.call(elsArray, function () {...}) - Kijewski
3
它不是一个NodeList,而是类似数组的对象。我甚至认为它没有实例类型。然而,querySelectorAll方法返回一个NodeList。 - Maksim Vi.
3
@MaksimVi,你说得非常正确:DOM4 规定 document.getElementsByClassName() 应该返回一个HTMLCollection(与 NodeList 非常相似但不是同一种类型)。感谢指出错误。 - Tim Down
1
@MaksimVi:我想知道这个是否在某个时候发生了改变。通常我会检查这些事情。 - Tim Down
2
@TimDown,感谢你的HTMLCollection提示。现在我终于可以在我的代码中使用HTMLCollection.prototype.forEach = Array.prototype.forEach;了。 - Maksim Vi.
显示剩余5条评论

149

您可以使用Array.from将集合转换为数组,这比Array.prototype.forEach.call更加简洁:

您可以使用Array.from将集合转换为数组,这比Array.prototype.forEach.call更加简洁:

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

在不支持Array.from的旧浏览器中,您需要使用像Babel这样的工具。


ES6 还添加了这种语法:

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

使用...进行Rest解构可以用于所有类似数组的对象,而不仅仅是数组本身,然后使用传统的数组语法从这些值中构造一个数组。


虽然替代函数querySelectorAll(有点让getElementsByClassName过时)返回一个集合,该集合本身具有forEach,但其他方法,如mapfilter缺失,因此这种语法仍然很有用:

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

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

7
注意:如果不像建议那样进行转译(使用Babel),这段代码将无法在IE < Edge、Opera、Safari < 9和Android浏览器以及Chrome for Android等浏览器中兼容。 - Sean

63

7
请记住,使用getElementByClassName会导致性能下降。 - Szabolcs Páll
11
与像修改DOM这样更加密集的任务相比,性能惩罚是可以忽略不计的。如果我在1毫秒内执行了60000个这样的操作,我确信对于任何合理的用途来说都不会成为问题 :) - icl7126
5
你链接了错误的基准测试。这是正确的链接:https://www.measurethat.net/Benchmarks/Show/4076/0/queryselectorall-vs-getelementsbyclassname 我刚在我的低端手机上测试了一下,得到了160k/s和380k/s的结果。由于你提到了DOM操作,这里也有相关内容:https://www.measurethat.net/Benchmarks/Show/5705/0/queryselectorall-vs-getelementsbyclassname-with-foreach 我得到了50k/s和130k/s的结果。如你所见,在处理DOM时甚至更慢,可能是由于NodeList是静态的(正如其他人所提到的)。在大多数情况下仍然可以忽略不计,但速度几乎慢了3倍。 - Szabolcs Páll
IE也不支持NodeList的forEach方法。但是有一个解决方法,就是使用spread运算符或Array.from。 - Melchia

18

getElementsByClassName 在现代浏览器中返回 HTMLCollection

它是一种类似于 arguments 的类数组对象,可以通过 for...of 循环进行迭代,以下是 MDN 文档的说明:

for...of 语句会创建一个循环用于迭代可迭代对象,包括:内置的 String、Array、类数组对象(例如 arguments 或 NodeList)、TypedArray、Map、Set 和用户自定义的可迭代对象。它调用一个自定义的迭代钩子,并为对象的每个不同属性的值执行语句。

Javascript 示例

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

Typescript 示例

let elements = document.getElementsByClassName('classname');
let i;

for (i = 0; i < elements.length; i++) {

  if (elements[i] instanceof HTMLElement) {
    elements[i].style.display = "none";
  }

}

1
但是 TypeScript 没有这样认为: 错误 TS2488: 类型 'HTMLCollectionOf<Element>' 必须有一个 '[Symbol.iterator]()' 方法,该方法返回一个迭代器。 - Turtles Are Cute
1
@TurtlesAreCute,这里的OP使用的是JavaScript而不是TypeScript,我根据vanilla js的建议进行了回答,因此在TypeScript中可能会有不同的解决方案。 - Haritsinh Gohil
1
@TurtlesAreCute,顺便说一下,它也适用于TypeScript,但是您必须指定保存特定CSS类元素的正确类型变量,以便可以相应地转换它,有关详细信息,请参见此答案 - Haritsinh Gohil
1
这绝对是最好的答案,在Typescript中也能很好地工作。 - Timmmm
1
@NateS 你可以将这个 HTMLCollection 存入变量中并声明其类型为 any,这样它就能像在 JavaScript 中一样工作。 - Haritsinh Gohil
显示剩余4条评论

18
getElementsByClassName() 的结果不是数组,而是一个类数组对象。具体来说,它被称为 HTMLCollection,不要与 NodeList 混淆(其拥有自己的 forEach() 方法)。
ES2015 中将类数组对象转换为可用于 Array.prototype.forEach() 的数组的一种简单方法是使用扩展运算符或 扩展语法
const elementsArray = document.getElementsByClassName('myclass');

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

3
我认为这真的是现代浏览器中正确的做法。这正是展开语法被创建来解决的确切用例。 - Matt Korostoff
1
让我声明一下,我可能是世界上最不称职的JavaScript程序员,而这个例子却在第一次尝试中对我有效。非常感谢你,Kloptikus。 - fuzzygroup

16

编辑:尽管返回类型在新版本的HTML中已更改(请参见Tim Down的更新答案),但下面的代码仍然可用。

正如其他人所说,它是一个NodeList。以下是一个完整的、可运行的示例供您尝试:

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

这在Win 7上的IE 9、FF 5、Safari 5和Chrome 12中有效。


1
已验证于2020年12月该代码仍然有效。 - james.garriss

6

对于TypeScript,我更喜欢使用 for 循环进行迭代。

for(let element of Array.from(document.querySelectorAll('.myclass'))){
   //my code
}

4

如前所述,getElementsByClassName返回一个被定义为HTMLCollection的对象。

[Exposed=Window]
interface <i>HTMLCollection</i> {
  readonly attribute unsigned long <a rel="nofollow noreferrer" href="https://www.w3.org/TR/dom/#dom-htmlcollection-length">length</a>;
  getter <a rel="nofollow noreferrer" href="https://www.w3.org/TR/dom/#element">Element</a>? <a rel="nofollow noreferrer" href="https://www.w3.org/TR/dom/#dom-htmlcollection-item">item</a>(unsigned long <i>index</i>);
  getter <a rel="nofollow noreferrer" href="https://www.w3.org/TR/dom/#element">Element</a>? <a rel="nofollow noreferrer" href="https://www.w3.org/TR/dom/#dom-htmlcollection-nameditem">namedItem</a>(DOMString <i>name</i>);
};

之前,一些浏览器返回的是 节点列表
[Exposed=Window]
interface <i>NodeList</i> {
  getter <a rel="nofollow noreferrer" href="https://www.w3.org/TR/dom/#node">Node</a>? <a rel="nofollow noreferrer" href="https://www.w3.org/TR/dom/#dom-nodelist-item">item</a>(unsigned long <i>index</i>);
  readonly attribute unsigned long <a rel="nofollow noreferrer" href="https://www.w3.org/TR/dom/#dom-nodelist-length">length</a>;
  iterable<<a rel="nofollow noreferrer" href="https://www.w3.org/TR/dom/#node">Node</a>>;
};

区别很重要,因为DOM4现在将 NodeList 定义为可迭代对象。
根据Web IDL草案,

实现声明为可迭代的接口的对象 支持被迭代以获取一系列值。

注意:在ECMAScript语言绑定中,一个可迭代的接口将具有“entries”、“forEach”、“keys”、“values”和 @@iterator属性在其接口原型对象上。

这意味着,如果您想使用 forEach ,则可以使用返回 NodeList 的DOM方法,例如querySelectorAll
document.querySelectorAll(".myclass").forEach(function(element, index, array) {
  // do stuff
});

请注意,这还没有被广泛支持。另请参见Node.childNodes的forEach方法?


1
Chrome 49 返回 forEach不是一个函数 - Vitaly Zdanevich
@VitalyZdanevich 试试Chromium 50 - Oriol
在Chrome 50上,我遇到了“document.querySelectorAll(...).forEach is not a function”的问题。 - Vitaly Zdanevich
@VitalyZdanevich 它在Chromium 50上运行良好,并且仍然可以在Chromium 53上运行。也许它不够稳定,无法被运送到Chrome 50。 - Oriol

3
这是更安全的方法:
var elements = document.getElementsByClassName("myclass");
for (var i = 0; i < elements.length; i++) myFunction(elements[i]);

3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接