【问题标题】:JS: iterating over result of getElementsByClassName using Array.forEachJS:使用 Array.forEach 迭代 getElementsByClassName 的结果
【发布时间】:2011-04-21 18:08:18
【问题描述】:

我想迭代一些 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 的结果是一个数组吗?如果不是,那是什么?

【问题讨论】:

    标签: javascript foreach getelementsbyclassname


    【解决方案1】:

    没有。作为specified in DOM4,它是HTMLCollection(至少在现代浏览器中。旧版浏览器返回NodeList)。

    在所有现代浏览器(几乎所有其他 IE forEach 方法,将元素列表(无论是 HTMLCollectionNodeList)作为 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);
    });
    

    【讨论】:

    • 无需先将其转换为数组。只需使用[].forEach.call(elsArray, function () {...})
    • 它不是 NodeList。它是一个类似数组的对象。我什至认为它没有实例类型。 querySelectorAll 方法虽然返回一个 NodeList。
    • @MaksimVi。你是绝对正确的:DOM4 指定 document.getElementsByClassName() 应该返回一个 HTMLCollection (这非常相似,但不是 NodeList)。感谢您指出错误。
    • @MaksimVi.:我想知道这是否在某个时候发生了变化。我通常会检查这些东西。
    • @TimDown,感谢HTMLCollection 提示。现在我终于可以在我的代码中使用HTMLCollection.prototype.forEach = Array.prototype.forEach;了。
    【解决方案2】:

    您可以使用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);
    

    【讨论】:

    • 注意:如果不按照建议进行转译(Babel),这在 IE Source: mozilla dev docs跨度>
    【解决方案3】:

    或者你可以使用返回NodeListquerySelectorAll

    document.querySelectorAll('.myclass').forEach(...)
    

    现代浏览器(包括 Edge,但不包括 IE)支持:
    Can I use querySelectorAll
    NodeList.prototype.forEach()

    MDN: Document.querySelectorAll()

    【讨论】:

    • 记住对 getElementByClassName 的性能损失
    • 与修改 DOM 等其他更密集的任务相比,性能损失可以忽略不计。如果我执行60,000 of these in 1 millisecond,我很确定它不会成为任何合理使用的问题:)
    • 您链接了错误的基准。这是正确的measurethat.net/Benchmarks/Show/4076/0/… 刚刚在我的低端手机上运行它,得到 160k/s 和 380k/s。既然你提到了 DOM 操作,这也是measurethat.net/Benchmarks/Show/5705/0/… 得到 50k/s 与 130k/s。如您所见,操作 DOM 甚至更慢,可能是因为 NodeList 是静态的(正如其他人所提到的)。在大多数用例中仍然可以忽略不计,但仍然慢了近 3 倍。
    • IE 也不支持 NodeList 的 forEach 方法。但是有一种解决方法,例如使用扩展运算符或 Array.from
    【解决方案4】:

    编辑:虽然返回类型在新版本的 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>
    

    这适用于 IE 9、FF 5、Safari 5 和 Win 7 上的 Chrome 12。

    【讨论】:

    • 已在 2020 年 12 月验证此代码仍然有效。
    【解决方案5】:

    getElementsByClassName() 的结果不是一个数组,而是一个类数组对象。具体来说,它称为HTMLCollection,不要与NodeList (which has it's own forEach() method) 混淆。

    使用 ES2015 转换类似数组的对象以供 Array.prototype.forEach() 使用的一种简单方法是使用扩展运算符或 spread syntax

    const elementsArray = document.getElementsByClassName('myclass');
    
    [...elementsArray].forEach((element, index, array) => {
        // do something
    });
    

    【讨论】:

    • 我觉得这在现代浏览器中确实是正确的做法。这是为解决问题而创建的确切用例传播语法。
    【解决方案6】:

    getElementsByClassName 在现代浏览器中返回 HTMLCollection

    这是 类似于参数的类数组对象,可通过for...of 循环迭代,请参阅下面的MDN doc 对此有何评论:

    for...of 语句创建一个循环遍历可迭代对象, 包括:内置字符串、数组、类数组对象(例如,参数 或 NodeList)、TypedArray、Map、Set 和用户定义的可迭代对象。它 调用自定义迭代钩子,其中包含要执行的语句 对象的每个不同属性的值。

    Javascript 示例

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

    打字稿示例

    let elements = document.getElementsByClassName('classname');
    let i;
    
    for (i = 0; i < elements.length; i++) {
    
      if (elements[i] instanceof HTMLElement) {
        elements[i].style.display = "none";
      }
    
    }
    

    【讨论】:

    • 不是这样,根据 Typescript:error TS2488: Type 'HTMLCollectionOf&lt;Element&gt;' must have a '[Symbol.iterator]()' method that returns an iterator.
    • @TurtlesAreCute,这里 OP 使用的是 javascript 而不是打字稿,我已经根据 vanilla js 推荐回答了,所以在打字稿中它可以是解决问题的不同解决方案。
    • @TurtlesAreCute,顺便说一下,它也在 typescript 中工作,但你必须提到正确的变量类型,它包含特定 css 类的元素,所以它可以相应地转换它,详情请参阅@ 987654322@.
    • 这很容易成为最佳答案,并且在 Typescript 中运行良好。
    • @NateS 你可以做的就是将这个HTMLCollection 放入变量中并声明它的类型为any,它将像在javascript中一样工作。
    【解决方案7】:

    getElementsByClassName 的结果是数组吗?

    没有

    如果不是,那是什么?

    与所有返回多个元素的DOM方法一样,它是一个NodeList,见https://developer.mozilla.org/en/DOM/document.getElementsByClassName

    【讨论】:

      【解决方案8】:

      如前所述,getElementsByClassName 返回一个HTMLCollection,其定义为

      [Exposed=Window]
      interface HTMLCollection {
        readonly attribute unsigned long @987654322@;
        getter @987654323@? @987654324@(unsigned long index);
        getter @987654325@? @987654326@(DOMString name);
      };

      以前,某些浏览器会返回 NodeList

      [Exposed=Window]
      interface NodeList {
        getter @987654328@? @987654329@(unsigned long index);
        readonly attribute unsigned long @987654330@;
        iterable<@987654331@>;
      };

      区别很重要,因为 DOM4 现在将 NodeLists 定义为可迭代的。

      根据Web IDL草稿,

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

      注意:在 ECMAScript 语言绑定中,接口是 iterable 将有“entries”、“forEach”、“keys”、“values”和 其interface prototype object 上的@@iterator 属性。

      也就是说,如果你想使用forEach,你可以使用一个返回NodeList的DOM方法,比如querySelectorAll

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

      请注意,这尚未得到广泛支持。另见forEach method of Node.childNodes?

      【讨论】:

      • Chrome 49 返回forEach in not a function
      • @VitalyZdanevich 试试 Chromium 50
      • 在 Chrome 50 上我收到 document.querySelectorAll(...).forEach is not a function
      • @VitalyZdanevich 它可以在 Chromium 50 上运行,并且仍然可以在 Chromium 53 上运行。也许它被认为不够稳定,无法发布到 Chrome 50。
      【解决方案9】:

      这是更安全的方法:

      var elements = document.getElementsByClassName("myclass");
      for (var i = 0; i < elements.length; i++) myFunction(elements[i]);
      

      【讨论】:

        【解决方案10】:

        这是我在 jsperf 上创建的测试: https://jsperf.com/vanillajs-loop-through-elements-of-class

        Chrome 和 Firefox 中性能最好的版本是结合 document.getElementsByClassName 的老式 for 循环:

        var elements = document.getElementsByClassName('testClass'), elLength = elements.length;
        for (var i = 0; i < elLength; i++) {
            elements.item(i).textContent = 'Tested';
        };
        

        在 Safari 中,这个变种是赢家:

        var elements = document.querySelectorAll('.testClass');
        elements.forEach((element) => {
            element.textContent = 'Tested';
        });
        

        如果您想要所有浏览器的最佳变体,它可能是这个:

        var elements = document.getElementsByClassName('testClass');
        Array.from(elements).map(
            (element) => {
                return element.textContent = 'Tested';
            }
        );
        

        【讨论】:

          【解决方案11】:

          它不返回Array,而是返回NodeList

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-04-12
            • 2011-12-12
            相关资源
            最近更新 更多