JS: iterando sobre o resultado de getElementsByClassName usando Array.forEach

240

Quero iterar sobre alguns elementos DOM, estou fazendo o seguinte:

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

mas eu recebo um erro:

document.getElementsByClassName ("myclass"). forEach não é uma função

Eu estou usando o Firefox 3, então eu sei que ambos getElementsByClassNamee Array.forEachestão presentes. Isso funciona bem:

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

O resultado de getElementsByClassNameuma matriz? se não, o que é?

Steve Claridge
fonte

Respostas:

384

Não. Conforme especificado no DOM4 , é um HTMLCollection(em navegadores modernos, pelo menos. Navegadores mais antigos retornaram a NodeList).

Em todos os navegadores modernos (praticamente qualquer outro IE <= 8), você pode chamar o forEachmétodo Array , passando a lista de elementos (seja HTMLCollectionou NodeList) como thisvalor:

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

Se você estiver na posição feliz de poder usar o ES6 (por exemplo, você pode ignorar com segurança o Internet Explorer ou está usando um transpiler ES5), você pode usar Array.from:

Array.from(els).forEach((el) => {
    // Do stuff here
    console.log(el.tagName);
});
Tim Down
fonte
29
Não há necessidade de convertê-lo em uma matriz primeiro. Apenas use [].forEach.call(elsArray, function () {...}).
Kay - SE is evil
1
NÃO é um NodeList. É um objeto parecido com um array. Eu nem acho que tem um tipo de instância. querySelectorAllO método retorna um NodeList.
Maksim Vi.
2
@MaksimVi. Você está absolutamente certo: o DOM4 especifica que document.getElementsByClassName()deve retornar um HTMLCollection(que é muito semelhante, mas não um NodeList). Obrigado por apontar o erro.
Tim baixo
@MaksimVi .: Gostaria de saber se isso mudou em algum momento. Eu costumo verificar essas coisas.
Tim baixo
1
@ TimDown, Obrigado pela HTMLCollectiondica. Agora finalmente posso usar HTMLCollection.prototype.forEach = Array.prototype.forEach;no meu código.
Maksim Vi.
70

Você pode usar Array.frompara converter coleção em matriz, o que é muito mais limpo que Array.prototype.forEach.call:

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

Em navegadores antigos que não suportam Array.from, você precisa usar algo como o Babel.


O ES6 também adiciona esta sintaxe:

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

Após a reestruturação com ...obras em todos os objetos do tipo matriz, não apenas as próprias matrizes, é usada uma boa sintaxe antiga de matriz para construir uma matriz a partir dos valores.


Enquanto a função alternativa querySelectorAll(que meio que torna getElementsByClassNameobsoleta) retorna uma coleção que possui forEachnativamente, outros métodos gostam mapou filterestão faltando, então essa sintaxe ainda é útil:

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

[...document.querySelectorAll(".myclass")].map(element => element.innerHTML);
Athari
fonte
6
Nota: sem transpilar como sugerido (Babel), isso NÃO é compatível no IE <Edge, Opera, Safari <9, navegador Android, Chrome para Android, ... etc) Fonte: mozilla dev docs
Sean
30

Ou você pode usar o querySelectorAllque retorna NodeList :

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

Compatível com navegadores modernos (incluindo Edge, mas não o IE):
Posso usar querySelectorAll
NodeList.prototype.forEach ()

MDN: Document.querySelectorAll ()

icl7126
fonte
4
Lembre-se da penalidade de desempenho sobre o getElementByClassName #
Szabolcs Páll
3
A penalidade de desempenho é insignificante em comparação com outras tarefas mais intensivas, como a modificação do DOM. Se eu executar 60.000 destes em 1 milissegundo , eu tenho certeza que ele não vai ser um problema para qualquer uso razoável :)
icl7126
1
Você vinculou a referência errada. Aqui é o correto measurethat.net/Benchmarks/Show/4076/0/... Apenas correu em meu telefone low-end, tem 160k / s vs 380k / s. Desde que você mencionou a manipulação do DOM, eis aqui que também medimos isso . Como você vê, é ainda mais lento manipular o DOM, provavelmente devido ao NodeList ser estático (conforme mencionado por outros). Ainda negligenciável na maioria dos casos de uso, mas quase três vezes mais lento.
Szabolcs Páll
14

Editar: embora o tipo de retorno tenha sido alterado nas novas versões do HTML (consulte a resposta atualizada de Tim Down), o código abaixo ainda funciona.

Como outros já disseram, é um NodeList. Aqui está um exemplo completo e prático que você pode tentar:

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

Isso funciona no IE 9, FF 5, Safari 5 e Chrome 12 no Win 7.

james.garriss
fonte
9

O resultado de getElementsByClassName()não é uma matriz, mas um objeto semelhante a uma matriz . Especificamente, é chamado de an HTMLCollection, para não ser confundido NodeList( que possui seu próprio forEach()método ).

Uma maneira simples com o ES2015 de converter um objeto do tipo matriz para uso Array.prototype.forEach()que ainda não foi mencionado é usar o operador de propagação ou a sintaxe de propagação :

const elementsArray = document.getElementsByClassName('myclass');

[...elementsArray].forEach((element, index, array) => {
    // do something
});
Kloptikus
fonte
2
Eu sinto que essa é realmente a maneira certa de fazer isso em navegadores modernos. Essa é a sintaxe exata da propagação do caso de uso que foi criada para resolver.
precisa saber é o seguinte
3

Como já foi dito, getElementsByClassNameretorna um HTMLCollection , que é definido como

[Exposed=Window]
interface HTMLCollection {
  readonly attribute unsigned long length;
  getter Element? item(unsigned long index);
  getter Element? namedItem(DOMString name);
};

Anteriormente, alguns navegadores retornavam um NodeList .

[Exposed=Window]
interface NodeList {
  getter Node? item(unsigned long index);
  readonly attribute unsigned long length;
  iterable<Node>;
};

A diferença é importante, porque agora o DOM4 define os NodeList como iteráveis.

De acordo com o rascunho do IDL da Web ,

Objetos que implementam uma interface declarada como suporte iterável são iterados para obter uma sequência de valores.

Nota : Na ligação de idioma do ECMAScript, uma interface iterável terá propriedades "entradas", "forEach", "keys", "values" e @@ iterator em seu objeto de protótipo de interface .

Isso significa que, se você quiser usar forEach, poderá usar um método DOM que retorne um NodeList , como querySelectorAll.

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

Observe que isso ainda não é amplamente suportado. Veja também o método forEach de Node.childNodes?

Oriol
fonte
1
Retorno do Chrome 49forEach in not a function
Vitaly Zdanevich 23/04
@VitalyZdanevich Experimente o Chromium 50
Oriol
No Chrome 50 Estou ficandodocument.querySelectorAll(...).forEach is not a function
Vitaly Zdanevich
@VitalyZdanevich Ele trabalhou no Chromium 50, e ainda funciona no Chromium 53. Talvez ele não foi considerado suficientemente estável para ser enviado para Chrome 50.
Oriol
1

Ele não retorna um Array, ele retorna um NodeList .

reko_t
fonte
1

Esta é a maneira mais segura:

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

getElementsByClassNameretorna HTMLCollection em navegadores modernos.

que é um objeto semelhante a um array semelhante aos argumentos iteráveis ​​por for...ofloop, veja abaixo o que o documento MDN está dizendo sobre ele:

A instrução for ... of cria um loop iterativo sobre objetos iteráveis , incluindo: String, Array, objetos do tipo Array (por exemplo, argumentos ou NodeList), TypedArray, Map, Set e iterables definidos pelo usuário. Ele chama um gancho de iteração personalizado com instruções a serem executadas para o valor de cada propriedade distinta do objeto.

exemplo

for (let element of getElementsByClassName("classname")){
   element.style.display="none";
}
Haritsinh Gohil
fonte
Não é assim, de acordo com o texto datilografado:error TS2488: Type 'HTMLCollectionOf<Element>' must have a '[Symbol.iterator]()' method that returns an iterator.
Turtles Are Cute
@TurtlesAreCute, aqui OP está usando javascript não datilografado e eu respondi de acordo com a recomendação de vanilla js, portanto, em datilografado pode ser uma solução diferente para o problema.
Haritsinh Gohil 01/01
@TurtlesAreCute, A propósito, ele também está trabalhando em texto datilografado, mas você deve mencionar o tipo certo de variável que contém um elemento de determinada classe css, para que ele possa ser convertido em conformidade, para obter detalhes, consulte esta resposta .
Haritsinh Gohil 01/01
0

Aqui está um teste que eu criei no jsperf: https://jsperf.com/vanillajs-loop-through-elements-of-class

A versão mais aperfeiçoada no Chrome e no Firefox é o bom e velho loop for combinado com document.getElementsByClassName:

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

No Safari, esta variante é a vencedora:

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

Se você deseja a variante mais aperfeiçoada para todos os navegadores, pode ser esta:

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