O que diferencia array, NodeList, HTMLCollection e outros iteráveis em Javascript

Não só de array vive a pessoa dev Javascript. Clique e veja os outros tipos de iteráveis, como nodelist, htmlcollection, domtokenlist...

  • array
  • nodelist
  • htmlcollection
  • array-like
  • javascript
  • domtokenlist
Front-end

Você sabia que arrays não são os únicos iteráveis em Javascript?

Nem nodelist...

Nem HTMLCollection...

Nesse post vou falar sobre todos esses, e mais dois: DOMTokenList e NamedNodeMap.

Ainda existem outros tipos, mas acredito que esses sejam os principais.

const socialMedias = [ 'youtube', 'twitter', 'facebook' ];

Vou começar com uma explicação que se aplica a todos eles.

O que são "listas" em Javascript

Listas são estruturas de dados muito comuns em Javascript.

Todas as características que vou listar aqui existem em todos os tipos de lista.

Cada item dentro da lista tem um índice, também chamado de index, que inicia em zero. E a forma mais comum de acessar um item é através da notação de colchetes:

socialMedias[0]; // 'youtube'
socialMedias[1]; // 'twitter'
socialMedias[2]; // 'facebook'

Para descobrir a quantidade de itens de uma lista, basta usar a propriedade length:

socialMedias.length; // 3

Com exceção da array, você vai perceber que costumam chamar as outras listas de array-like. Você se lembra da frase "é tipo Net"? É mais ou menos isso, é algo que parece array, porém não é array.

Array-like é tipo array porque possui características em comum com ela, mas nem tantas:

  • É uma lista

  • Permite percorrer ela

  • Pode usar o .length para descobrir seu tamanho

  • Pode acessar um item com a notação de colchetes

Mas nenhum array-like é tão completo quanto uma array em relação aos métodos disponíveis.

Por exemplo, se você tentar usar o método .map() em uma array-like, receberá o seguinte erro:

list.map is not a function

Veja agora os tipos de lista que o Javascript tem.

O que é um array em Javascript

Se você caiu nesse post, provavelmente trabalha muito com arrays.

Você pode usar métodos específicos para manipular as arrays de várias formas, como:

  • .push(newItem): adicionar um item no final da array

  • .unshift(newItem): adicionar um item no início da array

  • .pop(): remover o último item da array

  • .shift(): remover o primeiro item da array

  • .splice(): adicionar e/ou remover itens da array

Além disso, você pode usar métodos que transformam uma array em outras "coisas", sem alterar a array original. Todos essas funções recebem como parâmetro uma função. Os principais métodos são:

  • .forEach(): realiza uma operação para cada item da array. Exemplo: adicionar eventos em uma lista de botões

  • .map(): mapeia uma array para outra de mesmo tamanho, porém permite alterar cada elemento. Exemplo: transforma uma array de strings em caixa baixa em outra array em caixa alta

  • .filter(): filtra uma array e seleciona apenas os itens que atendem aos requisitos. Exemplo: cria uma array de números pares a partir de uma array que contém números pares e ímpares

  • .reduce(): talvez o mais chatinho de entender. Ele reduz uma array (lista) a apenas um valor. Exemplo: recebe uma array de objetos que representam produtos, e retorna a soma dos preços deles

  • .find(): procura em uma array o primeiro item que atende aos requisitos. Exemplo: pega em uma lista de objetos aquele com o atributo ID igual ao ID que você informou no parâmetro

  • .some(): retorna true ou false se pelo menos algum item corresponder aos requisitos

  • .every(): retorna true ou false somente se todos os itens corresponderem aos requisitos

Em Javascript, arrays são completamente dinâmicas. Isso significa que você pode alterar os itens, os tipos dos itens e a quantidade de itens delas.

Antes de continuar para o próximo tipo de lista, você precisa conhecer um detalhe sobre elas.

Diferença entre live collection e static collection

As listas podem ser vivas (live) ou estáticas (static).

Uma live collection atualiza a quantidade de itens quando você cria ou remove elementos da DOM.

Já uma static collection, não sofre alterações com updates na DOM.

Mas existem algumas exceções.

  • Ao alterar um atributo do elemento HTML, ele sempre atualiza independente do tipo de lista

  • Essas regras se aplicam apenas se você colocar a lista em uma variável. Se a cada momento você recriar a lista, ela sempre será "live"

O próximo tipo de lista que vou explicar, pode ser tanto live collection, quanto static collection. Depende.

O que é um Nodelist em Javascript

Um nodelist é uma lista muito semelhante à array, mas com algumas diferenças.

Além de ser um array-like, também é uma lista de nodes (sério?!). Pense em um node como um elemento HTML da DOM.

Uma limitação sua é não ter outros métodos úteis de array além do forEach.

Existem duas formas criar um nodelist.

A primeira é usar o método querySelectorAll() para pegar elementos. Com a linha abaixo, você recebe um nodelist de tags <section> da página.

document.querySelectorAll('section');
o retorno do queryselectorall é um nodelist

Isso irá te retornar uma static collection.

E a outra forma é com o atributo element.childNodes. Com ele, você recebe a lista de filhos de uma tag em uma live collection, e isso inclui textos puros e comentários. No exemplo abaixo, eu faço isso com os filhos de <body>:

document.body.childNodes;
o retorno do childnodes é um nodelist

Você percebeu que acessei o elemento <body> diretamente de document?

Essa é apenas uma das formas de acessar elementos da DOM sem usar métodos como o ou algo parecido.

Se você já usou o comando element.children, sabe que ele se parece com element.childNodes. A diferença eu explico já já, no capítulo sobre HTMLCollection.

Outra característica do nodelist é que, de todas as propriedades e métodos de array, ele tem apenas o forEach e o length:

nodelist tem acesso ao foreach e length

Existem outros arrays-like em Javascript. Outro bastante utilizado é o HTMLCollection.

O que é um HTMLCollection em Javascript

Vou cumprir minha promessa.

Lembra que falei sobre a semelhança entre element.childNodes e element.children? A diferença é que o element.children retorna os elementos filhos em um HTMLCollection, e não num nodelist. Ele também não pega textos e comentários.

Um HTMLCollection também representa elementos HTML, porém ele sempre é uma live collection.

Assim como um nodelist é mais limitado em relação a uma array, um HTMLCollection é limitado em relação um nodelist. Isso porque no dia a dia, o único atributo útil é o .length para descobrir a quantidade de itens.

getelementsbytagname gera um htmlcollection que tem acesso ao length

Existem algumas maneiras de criar um HTMLCollection. A primeira você já aprendeu, element.children. As outras formas são através dos métodos:

  • document.getElementsByClassName('my-class'): cria um HTMLCollection com os elementos que correspondem à classe que você informou no parâmetro

  • document.getElementsByTagName('div'): cria um HTMLColletion com os elementos que correspondem ao nome da tag que você informou no parâmetro

O próximo array-like é mais usado como uma lista de classes, e não de tags HTML.

O que é um DOMTokenList em Javascript

A forma mais comum de encontrá-lo no dia a dia é quando você acessa as classes de um elemento HTML.

classlist gera um domtokenlist

Diferente das estruturas de dados que mostrei até aqui, o DOMTokenList não é tão limitado em relação aos métodos. Como é muito comum manipular a lista de classes de um elemento HTML, ele já tem funções prontas para isso:

  • element.add('my-class'): adiciona uma classe

  • element.remove('my-class'): remove uma classe

  • element.toggle('my-class'): adiciona uma classe se ela não existir, remove se ela existir

  • element.contains('my-class'): verifica se o elemento contém uma classe e retorna true ou false

  • element.replace('old-class', 'new-class'): troca uma classe por outra

Veja agora a última array-like da lista.

O que é um NamedNodeMap em Javascript

Um NamedNodeMap contém os atributos de um elemento:

<button type="submit" class="submit-button" aria-label="Enviar dados">
  ENVIAR
</button>
const button = document.querySeletor('.submit-button');
console.log(button.attributes);

/*
{
  0: type,
  1: class,
  2: aria-label,
  type: type,
  class: class,
  aria-label: aria-label
}
*/

Ele tem uma aparência um pouco diferente das outras listas.

O NamedNodeMap fornece métodos para você manipular os atributos de um elemento. Por exemplo, você pode pegar um atributo com o getNamedItem():

button.attributes.getNamedItem('aria-label');

/*
{
  name: 'aria-label',
  value: 'Enviar dados'
}
*/

Remover um atributo com o removeNamedItem():

button.attributes.removeNamedItem('type');

/*
<button class="submit-button" aria-label="Enviar dados">
  ENVIAR
</button>
*/

Já o setNamedItem() adiciona ou altera o valor de um atributo. Porém achei sua aplicação confusa demais. Você precisa remover um atributo de outro elemento, antes de inseri-lo em outro. Ou então criar um atributo com o document.createAttribute():

const myAttribute = buttonA.removeNamedItem('class');
buttonB.attributes.setNamedItem(myAttribute);

// Ou

const myAttribute = document.createAttribute('class');
myAttribute.value = 'my-class';
button.attributes.setNamedItem(myAttribute);

Prefiro um caminho mais simples ao informar tudo como strings:

// Adiciona um atributo que não existe
button.setAttribute('type', 'button');

// Pega o valor desse atributo
button.getAttribute('type'); // 'button'

// Altera o valor
button.setAttribute('type', 'submit');

// Pega o valor desse atributo
button.getAttribute('type'); // 'submit'

// Remove um atributo
button.removeAttribute('type');

Esse é o primeiro post que recebo uma contribuição para o conteúdo. E dediquei o próximo capítulo a ela.

Por que os métodos de pegar elementos do DOM retornam iteráveis diferentes?

Essa pergunta veio diretamente do Camilo.

Conheci ele no Code in the Dark 2023, e é dono de um dos melhores @ do Twitter:

Existem algum motivos para isso. Vou resumir cada um deles, e depois deixar minha conclusão:

Live collection e Static collection

Um nodelist será uma lista estática na maioria das vezes que você usar. O mesmo não se pode dizer do HTMLCollection, que sempre será uma lista viva.

Então é possível afirmar se você precisa que seja uma lista dinâmica, o HTMLCollection é mais adequado.

A forma de gerar cada um deles

Como falei ao longo desse artigo, você gera essas duas listas de formas diferentes.

Você cria um nodelist com element.childNodes e querySelectorAll().

Já o HTMLCollection vem de element.children, document.getElementsByClassName() e document.getElementsByTagName().

Compatibilidade

Tanto o nodelist quando o HTMLCollection cobrem grande parte dos browser do mercado.

Mas tem uma pequena diferença. Nodelist fornece suporte para Internet Explorer 6 e 7 , HTMLCollection não .

caniuse mostrando que o nodelist é totalmente suportado no internet explorer
caniuse mostrando que o htmlcollection não é totalmente suportado no internet explorer

Mas quem se importa com o Internet Explorer 6 e 7?

Callback

O Javascript tem vários tipos de listas iteráveis, além do array.

Algumas delas são live collection, enquanto outras são estáticas.

A array é o tipo mais completo, com mais métodos e mais utilizado.

Nodelist é um array-like que armazena elementos HTML da DOM. Você gera um nodelist com .childNodes ou com document.querySelectorAll(). Os recursos que mais uso dele são .length e .forEach().

HTMLCollection sempre será uma lista viva. Ele é bem mais limitado em relação a recursos, e com exceção do .length não uso nada.

DOMTokenList é uma lista que você encontra principalmente na propriedade classList de um elemento. Ele possui métodos específicos para trabalhar com isso, como adicionar e remover classes.

NamedNodeMap é uma lista de atributos de uma tag HTML. Ele oferece métodos para manipular esses atributos, mas vejo uma complexidade desnecessária neles. Como expliquei no texto, prefiro usar os métodos diretamente dos elementos:

  • setAttribute

  • getAttribute

  • removeAttribute

Como você pode ver, as diferenças entre nodelist e HTMLCollection são pequenas.

Mas o problema é que elas se concentram apenas nos fatores anteriores à criação da lista. Nada da minha experiência ou do que encontrei na internet fala sobre casos de uso de cada uma. A única exceção é em relação à live collection e static collection.

No dia a dia, o querySelectorAll e o element.childNodes contemplam tudo e mais um pouco. Então crio nodelists com muito mais frequência.

Quase sempre converto tudo para array, pois possui mais métodos e é mais fácil de trabalhar.

Mas como converter qualquer lista para array? Vou detalhar os passos para fazer isso no futuro em outro post :)

Escrever esse artigo completo foi extremamente satisfatório, ainda pela contribuição da galera no Twitter. Assim como foi trabalhoso também hehe.

Se gostou desse conteúdo, envia para algum colega. Esse tema ou já passou pela cabeça de todo front-end, ou um dia vai passar.

Obrigado pela sua leitura.

Continue estudando:

Veja outros posts sobre Front-end