Desafio JavaScript entre duas amigas

(Última atualização em: 5 de julho de 2017)

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.

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 um olhar malicioso 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 post!

Para ter sempre em mãos informações que contribuirão para o seu desenvolvimento profissional, assine a nossa newsletter!

Twitter: @flaviohalmeida

Flávio Almeida é desenvolvedor e instrutor na Caelum e no Alura. Autor do livro MEAN "Full stack JavaScript para aplicações web com MongoDB, Express, Angular e Node", possui mais de 15 anos de experiência na área de desenvolvimento. Bacharel em Informática com MBA em Gestão de Negócios em TI, tem Psicologia como segunda graduação e procura aplicar o que aprendeu no desenvolvimento de software e na educação. Atualmente foca na plataforma Node.js e na linguagem JavaScript, tentando aproximar ainda mais o front-end do back-end. Já palestrou e realizou workshops em grandes conferências como QCON e MobileConf e esta sempre ávido por novos eventos.

  • Danilo Braga

    Opa! Já adicionei na lista de próximos cursos! Muito show!

    • Flávio Almeida

      Excelente! E o desafio entre amigas não parou ai! Teremos o próximo em breve! Aliás, um desafio mais puxado, porque Victoria pegou pesado com Maya! kkk

  • Saban

    Do livro: Use a cabeça! Flavio Almeida. Rs

    • Flávio Almeida

      Foi do livro do cangaço mesmo kkk. Use o chapéu!

  • Fanime Fartoon

    #atchinJava8 #atchinSeiLaQuemOJavaCopiou hahaha

  • Faltou falar da calopsita! 🙂

    • Flávio Almeida

      Kkkk

  • Pingback: JavaScript, debounce pattern, closure e duas amigas - Blog da Alura()

  • Bruno Paschoali

    Excelente post, Flávio! Muito bacana a forma como colocou a interação entre as personagens e as dicas de JS. Obrigado!

Próximo ArtigoVeja os 5 maiores fracassos e erros em lean startup