Desafio JavaScript entre duas amigas

Desafio JavaScript entre duas amigas
Flavio Henrique de Souza Almeida
Flavio Henrique de Souza Almeida

Compartilhe

Victoria foi desafiada por sua amiga Maya para demonstrar suas habilidades adquiridas no curso de JavaScript Avançado da Alura.

Maya disponibilizou para Victoria o seguinte HTML que exibe uma tabela com os nomes e as idades de três pessoas.


<!DOCTYPE html> 
<html> 
  <head> 
  <meta charset="utf-8"> 
  <meta name="viewport" content="width=device-width"> 
  <title>Dados</title> 
  </head> 
<body> 
  <table> 
    <thead> 
      <tr> 
        <th>Nome</th>
        <th>Idade</th>
      </tr> 
    </thead>
  <tbody> 
    <tr class="pessoa"> 
      <td class="nome">Breno</td>
      <td class="idade">19</td> 
    </tr> 
    <tr class="pessoa"> 
      <td class="nome">Maya</td> 
      <td class="idade">15</td> 
    </tr> 
    <tr class="pessoa"> 
      <td class="nome">Fernanda</td>
      <td class="idade">22</td> 
    </tr> 
  </tbody>
  <tfoot> 
    <td>Total idades</td>
    <td colspan="2" class="total"> ???</td> 
  </tfoot> 
  </table>
  <script> <!-- escrever sua lógica aqui --> </script> 
</body> 
</html>

O código anterior apresenta o seguinte resultado:


Nome
Idade
Breno
19
Maya
15
Fernanda
22
Total idades
???

A tarefa de Victoria será somar todos os valores da coluna Idade, exceto as idades que forem menores que 18 anos. O resultado deve ser inserido na td com a classe total. Considerando tudo que a Victoria tem que fazer, quais são os passos que ela precisa realizar?

Utilizando o querySelectorAll

Seu primeiro passo foi selecionar todos os elementos td com a classe idade. Ela utilizou document.querySelectorAll pois essa API do DOM aceita receber seletores CSS para buscar elementos e seu retorno será sempre um NodeList.


document.querySelectorAll('.idade')

Victoria explicou para Maya que um NodeList parece com um array e que ela poderia tratá-lo da mesma forma. Maya ficou ressabiada, pois reconhecia que arrays em JavaScript são poderosos.

Banner promocional da Alura, com um design futurista em tons de azul, apresentando o texto

Transformando um array com map

Sabendo que querySelectorAll retornava elementos do DOM que continham um texto com o valor das idades, ela lançou mão da função map para criar uma nova lista, contendo apenas os textos convertidos para números:


document.querySelectorAll('.idade')
  .map(td => parseInt(td.textContent))

Contudo, para a alegria de Maya, o seguinte erro ocorreu:

 Uncaught TypeError: document.querySelectorAll(...).map is not a function

Victoria lembrou que apesar de um NodeList ser parecido com um array ele não é, e por isso não possui a função map.

Uma ajudinha do spread operator

Ela precisava muito que o map funcionasse, porque fazia parte da sua estratégia. Então,ela lembrou do spread operator e fez o seguinte:


[...document.querySelectorAll('.idade')]
  .map(td => parseInt(td.textContent))

Os ... passaram para dentro do [] cada elemento individualmente e agora ela era capaz de usar a função map.

Maya olhou com certa desconfiança, mas reconheceu a validade do código e disse:

"Muito engenhosa essa sua solução para converter um NodeList para um Array".

Separando o que interessa com filter

Victoria já tinha uma lista de números, então ela só precisava filtrar essa lista considerando apenas as idades iguais ou maiores que 18 anos. Ela lançou mão da função filter que todo array possui:


[...document.querySelectorAll('.idade')]
  .map(td => parseInt(td.textContent)) 
  .filter(idade => idade >= 18)

Excelente! Agora, ela tinha certeza que no array filtrado só havia as idades 19 e 22. Maya gritou:

"Ainda falta totalizar!".

Totalizando com reduce

Sem pestanejar, Victoria usou a função reduce para reduzir os elementos de um array a um único valor:


const total = [...document.querySelectorAll('.idade')] 
  .map(td => parseInt(td.textContent))
  .filter(idade => idade >= 18) 
  .reduce((total, idade) => total + idade, 0);

Agora, de posse do total, ela só precisava inserí-lo na td correta. Contudo, Maya comentou:

"Por que você usou const?"

Victoria pacientemente explicou que o único local do seu programa que faz sentido a variável total receber uma atribuição de valor é neste ponto. Não faz sentido mais tarde alguém atribuir um novo valor para total, pois o valor que ele já guarda foi devidamente calculado com base nas idades lidas das páginas.

Sem deixar que Victoria acabasse de explicar, ela tomou as rédeas da conversa e disse:

"Eu sei, eu sei, acabei de lembrar. Variáveis declaradas com const não podem receber uma nova atribuição com o operador =."

Depois de fazer questão de mostrar para sua amiga que lembrava das razões do uso de const ela engatou um novo comentário:

"Aliás, você adora arrow function, usou com map, filter e reduce!".

Sobre o comentário da arrow function Victoria respondeu:

"É uma maneira mais sucinta de escrevermos funções, além de outras características que não são importantes para o problema que estou resolvendo, como o escopo léxico".

querySelectorAll vs querySelector

Em vez dela usar querySelectorAll, ela usou querySelector, pois este sempre retorna um elemento e não um array:


const total = [...document.querySelectorAll('.idade')]
 .map(td => parseInt(td.textContent)) 
 .filter(idade => idade >= 18) 
 .reduce((total, idade) => total + idade, 0); 
 document.querySelector('.total').textContent = total;

Escrevendo ainda menos

Victoria, não se contentando, decidiu remover a variável total deixando seu código ainda mais enxuto:


document.querySelector('.total').textContent = 
[...document.querySelectorAll('.idade')]
 .map(td => parseInt(td.textContent)) 
 .filter(idade => idade >= 18) 
 .reduce((total, idade) => total + idade, 0);

Uma homenagem ao jQuery

Ao ver o código, Maya disse:

"Incrível, você fez tudo isso sem declarar uma variável! Só ficou meio grandinho ter que escrever document.querySelector e document.querySelectorAll".

Victoria, com astúcia respondeu:

"Não tinha me preocupado com isso, mas posso enxugar o código desta forma, mas vou precisar de uma variável, aliás, farei uma homenagem ao jQuery usando a variável $".


const $ = document.querySelectorAll.bind(document);
 $('.total')[0].textContent = [...$('.idade')]
  .map(td => parseInt(td.textContent)) 
  .filter(idade => idade >= 18) 
  .reduce((total, idade) => total + idade, 0);

Maya ficou com um olhar perplexo ao ver a solução. Sem que ela dissesse alguma coisa, Victoria explicou:

"Eu criei um atalho para document.querySelectorAll guardando-a na variável $. Mas fazer simplesmente isso não funcionaria, porque $ perderia document como seu contexto, algo fundamental para que ela funcione. Daí, usei a função bind para criar uma nova função que mantivesse document como contexto. Aprendi isso estudando na Alura".

Maya sorriu.

Conclusão

No final da conversa, Maya elogiou o código de Victoria por ela ter combinado seus conhecimentos de manipulação de DOM, spread operator, conversão de tipos, map, filter e reduce de uma só vez para resolver o problema que lhe foi dado.

Sorrindo, Victoria disse:

"Só estava seguindo o caminho de uma cangaceira em JavaScript!".

Por fim, Maya pediu a Victoria que elaborasse um desafio para ela, nada mais justo depois de ter colocado a amiga à prova. Sabem qual desafio foi? Vocês só saberão no próximo artigo!

Twitter: @flaviohalmeida

Flavio Henrique de Souza Almeida
Flavio Henrique de Souza Almeida

Flávio é desenvolvedor e instrutor, focado no desenvolvimento com Angular e procurando conciliar o frontend com o backend. Palestrou em conferências como QCON e MobileConf. É autor do best-seller "Cangaceiro JavaScript". Além da sua graduação e MBA em TI, também é graduado em Psicologia, aplicando os aprendizados desta área no desenvolvimento de software e de aplicações web.

Veja outros artigos sobre Front-end