O já não tão novo ES6

Alex Santos
TOTVS Developers
Published in
9 min readApr 2, 2024

Lançado em 2015, o EcmaScript2015 ou ES6 ainda não é utilizado por muitos desenvolvedores. Algumas vezes, por falta de conhecimento, outras, puramente por má vontade em se atualizar mesmo.
Mas o quê têm de tão importante nessa versão? É justamente a semântica e clareza que faltam em alguns códigos do nosso dia a dia.

Photo by Growtika on Unsplash

Sempre há várias maneiras de se fazer o mesmo, e é justamente visando atacar esses problemas que são criados os Design Patterns, sendo uma solução geral para um problema que ocorre com frequência em um determinado contexto, no projeto de software. Ele visa solucionar o problema macro, ao definir uma arquitetura.
Com o passar do tempo, cada linguagem vai se aperfeiçoando, e no JavaScript isso não foi diferente. Ao analisar várias formas de uso de alguns métodos, os mesmos foram destrinchados, otimizados e tiveram uma melhora notável.

Template String:

Sem dúvidas o Template String é uma das coisas mais fáceis e divertidas de se começar a usar. Chega de “maiszinhos (+)” para ficar concatenando String!
Para utilizar o Template String, basta utilizar duas crases, que fará a função das aspas, a partir daí podemos interpolar variáveis dentro dela, utilizando cifrão seguido de chaves:

let name = "Alex";
let surname = "Santos";

//Forma antiga:
let fullName = name + " " + surname;

//Template string
let fullName = `${name} ${surname}`;

Arrow Functions:

As Arrow Functions vieram para simplificar a nossa escrita de funções. Normalmente declarávamos uma função com a palavra reservada function, seguida dos argumentos e abertura de chaves. Agora é possível usar as famosas Arrow Functions, porém precisamos saber que elas não são nomeadas, exceto se atribuídas a uma variável.
Vamos comparar uma função com a sintaxe tradicional e com a nova sintaxe:

function sum(firstNum, secondNum) {
return firstNum + secondNum;
}
//Arrow function
const sum = (firstNum, secondNum) => firstNum + secondNum;

//Arrow functions com apenas um argumento, não é necessário parênteses.
const log = text => console.log(text);
log("Hello World")

A diferença entre as funções são: na forma tradicional, precisamos nomear a função, caso seja uma callback, ainda sim precisamos da palavra function seguida dos parênteses com argumentos e o bloco, determinado pelas chaves.
Já na Arrow Function, podemos atribuir para uma variável, já que ela não é nomeada, e caso o bloco tenha apenas uma declaração, não precisa utilizar a palavra return. Porém, se tiver mais que uma declaração, é necessário utilizar as chaves e caso tenha retorno, utilizar o return. As Arrow Functions dão mais legibilidade quando estamos utilizando funções de CallBack.
Também não é necessário o uso de parênteses quando é declarado apenas um único argumento.

Destructuring

A desestruturação é algo muito interessante, pois ela nos permite não precisar ficar consumindo várias linhas de código para capturar os valores dos objetos. Um ponto de atenção é que a cada captura, estamos atribuindo a uma variável nova, e isso custa memória, mas nada que o Garbage Collector não consiga dar conta, em alguns casos.
Seguindo o exemplo acima, temos um objeto Pessoa, que tem nome e sobrenome, e para capturar os atributos, precisávamos usar o operador de acesso .
Para utilizar a desestruturação, declaramos uma variável com o mesmo nome do atributo entre chaves:

const person = { name: "Alex", surname: "Santos" }

// Forma antiga:
console.log(`Nome completo: ${person.name} ${person.surname}`)

// Forma nova:
const { name, surname } = person;
console.log(`Nome completo: ${name} ${surname}`)

O ganho disso é que não precisamos ficar referenciando sempre o objeto, dado que já extraímos os seus atributos para variáveis.
Outra funcionalidade legal é que podemos renomear as nossas variáveis, utilizando dois pontos:

const person = { name: "Alex", surname: "Santos" }
let { name: newName } = person
console.log(newName); //Alex

Também é possível utilizar a desestruturação em um método, passando um único objeto, sem a necessidade de ficar segregando os argumentos:

const person = { name: "Alex", surname: "Santos" }
let getFullName = ({ name, surname }) => {
return `Nome completo: ${name} ${surname}`;
}
console.log(getFullName(person)); // Nome completo: Alex Santos

Em alguns casos, existe a necessidade de desestruturar objetos aninhados, sem a precisar acessar os atributos com o operador de acesso .

const person = { name: "Alex", surname: "Santos", identification: { cpf: "123.456.078-99" } }
const { identification: { cpf: cpfIdentifier } } = person
console.log(cpfIdentifier); //123.456.078-99

Spread:

Trabalhar com listas é algo muito comum no desenvolvimento de software, e o ES6 não deixou isso de lado. O operador Spread, demarcado por (…), espalha os nossos itens da lista, e até atributos de objetos.

Um bom exemplo é o merge de uma lista, para fazer isso, normalmente precisaríamos percorrer as duas listas, adicionando em uma terceira, mas com o spread é muito mais simples, pois ele já faz isso por nós:

let approvedStudents = ["Alex", "Théo", "Mathias"]
let unapprovedStudents = ["Roberta", "Astolfo"]
let allStudents = [...approvedStudents, ...unapprovedStudents]
console.log(allStudents); //[ 'Alex', 'Théo', 'Mathias', 'Roberta', 'Astolfo' ]

Podemos aplicar o Spread também sobre objetos, e em algumas ocasiões pode ser útil:

const person = { name: "Alex", surname: "Santos", identification: { cpf: "123.456.078-99" } }
let clonePerson = { ...person }
console.log(clonePerson); //{ name: "Alex", surname: "Santos", identification: { cpf: "123.456.078-99" } }

Array Methods:

Os métodos de Array, sem dúvidas, é algo que se você não está usando, deveria começar a repensar fortemente sobre isso.
Na forma antiga, para convertermos as nossas listas de objetos em outros modelos, filtrar, validar ou efetuar operações, usávamos o forEach. Porém, ao estarmos efetuando um laço, muitas coisas podem estar acontecendo dentro dele, e para entender o contexto, precisamos ler o código inteiro, ou o desenvolvedor extrair para um método com nome semântico.

Uma biblioteca que foi muito utilizada, que faz esses métodos semânticos é o Lodash, referenciado pelo carácter _”. É disposto uma porção de métodos, que podemos mutar, filtrar e alterar as nossas listas de objetos de uma maneira semântica, que no final, estaria encapsulando os laços dentro dos métodos. Dado isso, o ES6 trouxe isso nativamente, e iremos comparar na prática alguns exemplos:

Map:

Utilizado para retornar outro modelo, o Map é continuamente utilizado para conversões. Dado uma lista de objetos, é necessário retornar apenas o modelo novo, que ele mesmo vai coletando e te devolve uma lista nova com o modelo já alterado:

let adresses = [{ zipCode: "01047-000", street: "Rua Marconi" }, { zipCode: "11740-00", street: "Rua Sapetuba" }]
// Efetua a conversão do objeto para o modelo novo.
let streets = address.map(function (adress) {
return adress.street;
});
// Simplificação utilizando Arrow function
let streets = address.map((adress) => adress.street);

Filter:

Em qualquer função na forma antiga, era sempre necessário declarar um Array e ir coletando os novos objetos. No Filter não é diferente, porém ele já faz isso por nós. A função dele é justamente o que o nome diz: filtrar, dado uma condição.
Vamos fazer um exemplo de capturar apenas nomes que começam com a letra “A”.

let names = ["Alex", "Aline", "Cristine"];
//Forma antiga:
let namesStartsWithA = [];
names.forEach(function (name) {
if (name.startsWith("A")) {
namesStartsWithA.push(name)
}
})
// Forma nova
let namesStartsWithA = names.filter((name)=> name.startsWith("A"))

Note, a comparação com forma antiga é muito mais verbosa, e precisamos ler o código para entender que estamos filtrando apenas os objetos que satisfaçam tal condição.

Find:

O Find é diferente do Filter, utilizamos ele quando queremos trazer apenas o primeiro elemento que satisfaça a condição booleana declarada, ou seja, ele irá fazer um “early return” na primeira condição verdadeira.
Por exemplo:

let names = ["Mathias Santos", "Théo Santos", "Mathias Oliveira"]
let foundedName = names.find(name => name.includes("Mathias"))
console.log(foundedName) //Mathias Santos
console.log(typeof foundedName) //string

O que é interessante analisar, é que independente de quantos nomes incluir “Mathias”, retornará apenas uma única ocorrência, e como o tipo da lista declarada é String, ele retornará uma única String, e não um Array com apenas um elemento.

Every / Some:

As funções de Every e Some são interessantes para analisarmos a condição que precisamos aplicar em cada situação. Um exemplo do mundo real é: se todos os processos deram sucesso, podemos seguir para a próxima etapa. Se algum aluno passou de ano, podemos começar a criar a nova turma. Esses métodos retornam um boolean, pois estamos apenas comparando se todos ou se algum objeto satisfaz alguma condição:

//Irá passar por toda lista, valorizando a condição.
let processes = [{ name: "register", status: true }, { name: "emailConfirmation", status: true }, { name: "login", status: true }];
let isProcessedSuccess = processes.every(process => process.status)
//Irá parar no primeiro true, ignorando o resto da lista.
let approvedList = [{ name: "Théo", isApproved: true }, { name: "Roberta", isApproved: false }, { name: "Mathias", isApproved: true }];
let hasStudentToNextClassRoom = approvedList.some(student => student.isApproved)

É interessante analisarmos também que certas funções são mais performáticas do que outras, e isso muda completamente o nosso algoritmo.

Encadeamento de funções:

Iremos nos deparar na necessidade de encadear funções, pois ficar declarando para variáveis separadas e aplicar outro tipo de funções pode ficar confuso. Temos que tomar bastante cuidado para também não prejudicar a legibilidade, pois com o poder de processamento atual, declarar uma variável a mais, pode não fazer tanta diferença assim, porém, precisaremos tomar decisões com base em cada cenário. Um bom exemplo é a utilização em conjunto do Filter e do Map:

let approvedList = [{ name: "Théo", isApproved: true }, { name: "Roberta", isApproved: false }, { name: "Mathias", isApproved: true }];
let approvedNames = approvedList.filter(student => student.isApproved)
.map(student => student.name)
console.log(approvedNames); // [ 'Théo', 'Mathias' ]

Nesse exemplo o Filter irá retornar um Array, e ele logo será encadeado para o Map. Não foi necessário segregar as variáveis, apenas utilizamos a “calda” do retorno.

Reduce:

Deixei essa função por último, pois, a meu ver, ela é uma das mais difíceis de se entender. O Reduce pode ser aplicado como uma função de soma ou subtração em alguns casos, mas ele é muito, mas muito mais poderoso do que isso.
Imagina que exista uma lista, e nela é necessária aplicar uma função de reduzir para outra forma, tipo um molho mesmo. Para reduzir o molho, precisamos ter controle sobre o fogo e a velocidade de movimento da colher. O Reduce nos entrega justamente isso: saímos a partir de uma lista, mas também temos controle sobre o valor inicial, que pode ser uma lista ou um objeto.
A particularidade nessa função é que o primeiro argumento é uma função, contendo o nosso objeto que queremos ter controle, e o iterável da vez, e o segundo argumento é o valor inicial que o nosso objeto irá ter, sendo uma lista vazia ou populada, ou um objeto:

/**
* Exemplo de soma de todos os números:
* O amount é o nosso valor declarado, que é o último argumento da função, que é 0.
* A cada iteração é feita uma soma e atribuição do valor anterior com o número atual.
*/
let sum = [1, 2, 3, 4].reduce((amount, currentNumber) => amount += currentNumber, 0)

/**
* Exemplo de recuperação de números duplicados.
* Passa um array vazio como o valor inicial e insere caso não exista o número.
* Poderia ser resolvido com um Set, mas isso é assunto para outro artigo.
*/
let duplicatedNumbers = [1, 2, 2, 3, 3, 4, 5, 6, 7, 4];
let uniqueNumbers = duplicatedNumbers.reduce((newNumbers, nextNumber) => {
let existsNumber = newNumbers.find((number) => number === nextNumber)
if (!existsNumber) {
newNumbers.push(nextNumber)
}
return newNumbers;
}, [])

Separei um bloco exclusivo para algo bastante legal, e um pouco complexo: recursividade. Ela pode ser aplicada em todos os Array Methods, e também funcionará em conjunto com os mesmos, mas dado que temos controle sobe o nosso Array declarado no Reduce, fica mais fácil a aplicação:

let menus = [
{ "name": "Address", submenus: [{ name: "Create" }, { name: "Report" }] },
{ "name": "Client" },
{ name: "Account", submenus: [{ name: "Operations" }] }
]

let getTextMenus = (menus = []) => menus.reduce((textMenus, currentItem) => {
if (currentItem.submenus) {
let functions = getTextMenus(currentItem.submenus);
textMenus.push(`${currentItem.name}: [${functions.join(', ')}]`)
return textMenus;
}
textMenus.push(currentItem.name)
return textMenus;
}, [])

console.log(getTextMenus(menus));
//[ 'Address: [Create, Report]', 'Client', 'Account: [Operations]' ]

Nesse exemplo, temos uma lista de menus, que podem ou não ter submenu. Declaramos a função getTextMenus, onde recebemos uma lista de menus, caso não receba nada, passa como Default Argument um Array vazio para não explodir exceção.
Começamos a reduzir, passando um valor padrão como um Array vazio. Caso haja um submenu, chamamos a função novamente, onde ele vai cair no próximo push, e irá colocar o nome de submenu. Tudo isso ocorrerá de forma recursiva, e teremos no primeiro bloco do if o valor de todos os submenus daquele menu, com isso fazemos um push no nosso Array Default com nome do menu concatenado com os submenus dele. Caso não haja, ele não entra na recursão.

Até conseguiríamos fazer esse cenário com um Map, porque ele está transformando o objeto, porém precisaríamos utilizar um Array auxiliar para ficar colocando as transformações dentro dele a cada iteração.

Algo importante a se lembrar é que sua função do Reduce precisa retornar um valor para ser atribuído ao objeto default/inicial, pois ele será re-chamada como o primeiro argumento da sua função na próxima iteração.

Considerações:

Esse é um artigo básico, para justamente te instigar a seguir os ensinamentos do ET Bilú e buscar conhecimento. Tem vários materiais para te guiar, e seguir práticas mais atualizadas de desenvolvimento. Surgiram várias novidades no ES6 que vão bem mais além do que descrito aqui. Espero que contribua com o aprendizado de alguém. Agradeço a leitura, e até breve!

Referências:

https://developer.mozilla.org/pt-BR/docs/Web/JavaScript

--

--

Alex Santos
TOTVS Developers

Um amante de música e tecnologia, apaixonado por desenvolver soluções.