O que diferencia array, NodeList, HTMLCollection e outros iteráveis em Javascript
Tags: array, nodelist, htmlcollection, githubbrasil
Published: 2023-08-21 11:19:48

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

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.
 
```javascript
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:
 
```javascript
socialMedias[0]; // 'youtube'
socialMedias[1]; // 'twitter'
socialMedias[2]; // 'facebook'
```
 
Para descobrir a quantidade de itens de uma lista, basta usar a propriedade `length`:
 
```javascript
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.
 
```javascript
document.querySelectorAll('section');
```
 
![o retorno do queryselectorall é um nodelist](https://demenezes.dev/_next/image/?url=https%3A%2F%2Fwww.datocms-assets.com%2F64896%2F1692324667-o-retorno-do-queryselectorall-e-um-nodelist.png%3Fauto%3Dformat&w=3840&q=75)
 
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>`:
 
```javascript
document.body.childNodes;
```
 
![o retorno do childnodes é um nodelist](https://demenezes.dev/_next/image/?url=https%3A%2F%2Fwww.datocms-assets.com%2F64896%2F1692324696-o-retorno-do-childnodes-e-um-nodelist.png%3Fauto%3Dformat&w=3840&q=75)
 
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](https://demenezes.dev/posts/7-alternativas-a-queryselectorall-queryselector-getelementbyid/) 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](https://demenezes.dev/_next/image/?url=https%3A%2F%2Fwww.datocms-assets.com%2F64896%2F1692324742-nodelist-tem-acesso-ao-foreach-e-length.png%3Fauto%3Dformat&w=3840&q=75)
 
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](https://demenezes.dev/_next/image/?url=https%3A%2F%2Fwww.datocms-assets.com%2F64896%2F1692324791-getelementsbytagname-gera-um-htmlcollection-que-tem-acesso-ao-length.png%3Fauto%3Dformat&w=3840&q=75)
 
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](https://demenezes.dev/_next/image/?url=https%3A%2F%2Fwww.datocms-assets.com%2F64896%2F1692324823-classlist-gera-um-domtokenlist.png%3Fauto%3Dformat&w=3840&q=75)
 
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:**
 
```html
<button type="submit" class="submit-button" aria-label="Enviar dados">
  ENVIAR
</button>
```
 
```javascript
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()`:
 
```javascript
button.attributes.getNamedItem('aria-label');

/*
{
  name: 'aria-label',
  value: 'Enviar dados'
}
*/
```
 
Remover um atributo com o `removeNamedItem()`:
 
```javascript
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()`:
 
```javascript
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:
 
```javascript
// 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](https://demenezes.dev/posts/front-in-sampa-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](https://caniuse.com/?search=nodelist), [HTMLCollection não](https://caniuse.com/?search=htmlcollection).
 
![caniuse mostrando que o nodelist é totalmente suportado no internet explorer](https://demenezes.dev/_next/image/?url=https%3A%2F%2Fwww.datocms-assets.com%2F64896%2F1692324866-caniuse-mostrando-que-o-nodelist-e-totalmente-suportado-no-internet-explorer.png%3Fauto%3Dformat&w=3840&q=75)
 
![caniuse mostrando que o htmlcollection não é totalmente suportado no internet explorer](https://demenezes.dev/_next/image/?url=https%3A%2F%2Fwww.datocms-assets.com%2F64896%2F1692324871-caniuse-mostrando-que-o-htmlcollection-nao-e-totalmente-suportado-no-internet-explorer.png%3Fauto%3Dformat&w=3840&q=75)
 
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:
 
- [https://daily.dev/blog/why-do-you-need-to-know-about-array-like-objects](https://daily.dev/blog/why-do-you-need-to-know-about-array-like-objects)
- [https://www.capscode.in/blog/what-is-array-like-object-in-javascript](https://www.capscode.in/blog/what-is-array-like-object-in-javascript)
- [https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Array](https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Array)
- [https://developer.mozilla.org/pt-BR/docs/Web/API/NodeList](https://developer.mozilla.org/pt-BR/docs/Web/API/NodeList)
- [https://www.w3schools.com/js/js_htmldom_nodelist.asp](https://www.w3schools.com/js/js_htmldom_nodelist.asp)
- [https://developer.mozilla.org/en-US/docs/Web/API/Element/children](https://developer.mozilla.org/en-US/docs/Web/API/Element/children)
- [https://developer.mozilla.org/pt-BR/docs/Web/API/HTMLCollection](https://developer.mozilla.org/pt-BR/docs/Web/API/HTMLCollection)
- [https://www.w3schools.com/jsref/dom_obj_htmlcollection.asp](https://www.w3schools.com/jsref/dom_obj_htmlcollection.asp)
- [https://medium.com/@layne_celeste/htmlcollection-vs-nodelist-4b83e3a4fb4b](https://medium.com/@layne_celeste/htmlcollection-vs-nodelist-4b83e3a4fb4b)
- [https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList](https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList)
- [https://www.w3schools.com/jsref/dom_obj_html_domtokenlist.asp](https://www.w3schools.com/jsref/dom_obj_html_domtokenlist.asp)
- [https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap](https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap)
- [https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap/getNamedItem](https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap/getNamedItem)
- [https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap/setNamedItem](https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap/setNamedItem)
- [https://www.w3schools.com/jsref/met_namednodemap_setnameditem.asp](https://www.w3schools.com/jsref/met_namednodemap_setnameditem.asp)
- [https://dom.spec.whatwg.org/#interface-nodelist](https://dom.spec.whatwg.org/#interface-nodelist)
- [https://dom.spec.whatwg.org/#interface-htmlcollection](https://dom.spec.whatwg.org/#interface-htmlcollection)
- [https://caniuse.com/?search=htmlcollection](https://caniuse.com/?search=htmlcollection)
- [https://caniuse.com/?search=nodelist](https://caniuse.com/?search=nodelist)