No dia a dia, como desenvolvedores, é bem frequente a necessidade de utilizarmos técnicas já conhecidas para determinadas funções, como ordernar, buscar um valor único, trocar valores, e assim por diante. Por vezes, estamos acostumados a fazer da mesma maneira, utilizando os mesmos padrões e algoritmos.
Neste artigo, veremos algumas maneiras de realizar tarefas do dia a dia, de maneira que talvez, você ainda não conheça. E assim, avançar para se tornar um desenvolvedor Pro.
É claro, que a solução para um problema pode ser alcançada de várias formas, aqui tentaremos buscar a forma mais eficiente e produtiva para a tarefa, e utilizando recursos que a própria linguagem, JavaScript, nos disponibiliza.
Este artigo tem como objetivo fornecer uma visão de como alcançar novas soluções, diminuindo o número de linha de código, legibilidade do código, e tornando você mais apto para participar de competições, entrevistas, trabalho, seja qual for o desafio.
Para fins de informação, todos os códigos abaixo estão sendo testados no console do Google Chrome, da última versão da data em que este artigo foi publicado.
Vamos lá. 🚀
1. Encontrar a soma, o valor máximo e mínimo de um array de inteiros ou array de objetos
Primeiramente, precisamos deixar claro que o JavaScript já disponibiliza algumas funcionalidades padrões para recuperar o valor máximo e mínimo de um objeto, como pode ver a seguir:
const values = [1,6,9,3,2,5,4,2]
Math.max(...values)
//output: 9
Math.min(...values)
//output: 1
Porém, quando se trata de operações mais complexas, e quando o número de elementos de uma lista aumenta drasticamente, pode ser que não seja tão performático quanto se deseja. Por exemplo, no código anterior, o operador Spread (…values), acaba por tornar a operação mais lenta. É claro que isso é praticamente imperceptível, e no dia a dia você provavelmente não precisará se preocupar inicialmente com micro otimizações, mas é algo que vale a pena ser apontado aqui.
Como dica extra, você pode comparar a performance de terminados códigos através desta ferramenta: https://jsbench.me/.
Agora vamos ao modelo alternativo para a mesma funcionalidade, e outras. Para fins didáticos, estou utilizando variáveis bem descritivas.
Somando valores inteiros de um array de inteiros
const values = [1,6,9,3,2,5,4,2]
values.reduce((sum, currentValue) => sum + currentValue)
//output: 32
Obs.: Quando não é informado o segundo parâmetro do método reduce, o sum passa a ser o primeiro valor do array.
Encontrando o valor máximo e mínimo de um array de inteiros
const values = [1,6,9,3,2,5,4,2]
// Máximo
values.reduce((max, currentValue) => max > currentValue ? max : currentValue);
//output: 9
// Mínimo
values.reduce((min, currentValue) => min < currentValue ? min : currentValue);
//output: 1
Vamos tornar as coisas mais interessantes. Agora, como faríamos se quiséssemos realizar as mesmas operações em array de objetos? Por exemplo, achar o somatório de preços de uma lista de produtos.
Vejamos a seguir:
const products = [
{name: 'prod 1', price: 1.9},
{name: 'prod 2', price: 2.1},
{name: 'prod 3', price: 8.9},
{name: 'prod 4', price: 4.5}
]
// Somatório
products.reduce((sum, currentProduct) => sum + currentProduct.price, 0)
//output: 17.4
// Produto mais caro
products.reduce((maxProd, currentProd) => maxProd.price > currentProd.price ? maxProd: currentProd)
//output: {name: 'prod 3', price: 8.9}
// Produto mais barato
products.reduce((minProd, currentProd) => minProd.price < currentProd.price ? minProd: currentProd)
//output: {name: 'prod 1', price: 1.9}
Vale ressaltar que o valor, de maxProd e minProd dos exemplos anteriores, sempre será inicialmente o primeiro valor do array, caso não seja definido o valor inicial do segundo parâmetro do reduce.
2. Ordenando arrays de strings, números e objetos
O JavaScript disponibiliza funções apropriadas para ordenação dos valores de um array. Como veremos a seguir, a ordenação por String é bem simples. Porem, as tarefas que enfrentamos são muitas vezes bem mais complexas, então nós vamos além. Iremos customizá-las para que possamos realizar a ordenação de array de valores numéricos, e até de objetos ordenando por seus campos internos.
Nos exemplos a seguir, tente assimilar o potencial dessas funcionalidades para quando a hora chegar, você utilizar esse conhecimento como um desenvolvedor avançado.
Ordenando por String
const values = ['Peter', 'Tony', 'Bruce', 'Lex']
values.sort()
// output: ['Bruce', 'Lex', 'Peter', 'Tony']
values.reverse()
// output: ['Tony', 'Peter', 'Lex', 'Bruce']
Ordenando por Número
const values = [3, 11, 99, 35, 11]
values.sort((a, b) => a - b)
// output: [3, 11, 11, 35, 99]
values.sort((a, b) => b - a)
// output: [99, 35, 11, 11, 3]
Ordenando Array de Objetos por campo numérico e campo string
const persons = [
{name: 'Peter', age: 52},
{name: 'Tony', age: 26},
{name: 'Bruce', age: 33},
{name: 'Lex', age: 77}
]
// *** ORDENAÇÃO POR CAMPO NUMÉRICO ***
persons.sort((a,b) => a.age - b.age)
// output:
// 0: {name: 'Tony', age: 26}, 1: {name: 'Bruce', age: 33},
// 2: {name: 'Peter', age: 52}, 3: {name: 'Lex', age: 77}
persons.sort((a,b) => b.age - a.age)
// output:
// 0: {name: 'Lex', age: 77}, 1: {name: 'Peter', age: 52},
// 2: {name: 'Bruce', age: 33}, 3: {name: 'Tony', age: 26}
// *** ORDENAÇÃO POR CAMPO STRING ***
persons.sort((a, b) => a.name.localeCompare(b.name));
// output:
// 0: {name: 'Bruce', age: 33}, 1: {name: 'Lex', age: 77},
// 2: {name: 'Peter', age: 52}, 3: {name: 'Tony', age: 26}
persons.sort((a, b) => b.name.localeCompare(c.name));
// output:
// 0: {name: 'Tony', age: 26}, 1: {name: 'Peter', age: 52},
// 2: {name: 'Lex', age: 77}, 3: {name: 'Bruce', age: 33}
O localeCompare, como mostrado no exemplo, é utilizado para comparar duas strings, e o seu retorno é -1, 1 ou 0. Sendo -1 quando a string base é inferior, 1 quando a string base é superior, e 0 quando ambas são iguais.
Para saber mais a respeito do localeCompare, acesse https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare.
3. Remoção de valores duplicados
Provavelmente você já se deparou com a necessidade de remover valores duplicados em JavaScript, seja de uma lista numérica ou de string simples, ou até mesmo de objetos. Logo abaixo, utilizaremos duas abordagens para esta tarefa, métodos nativos do array, e também uma que envolve a transformação de um array num novo objeto Set e sua desestruturação.
Remoção de valores duplicados de um array numérico ou de strings
const values = [3, 9, 3, 5, 2, 9]
// ou const values = ['a', 'b', 'a', 't', 'c', 'b']
// Utilizando métodos do array
values.filter((currentValue, index, arr) => arr.indexOf(currentValue) === index);
// Ou utilizando o Set para criar um objeto de valores únicos e
// desestruturando-o em um novo array
const uniques = [...new Set(values)]
// outputs: (4) [3, 9, 5, 2]
Remoção de valores duplicados de um array de objetos
const persons = [
{name: 'Peter'},
{name: 'Tony'},
{name: 'Bruce'},
{name: 'Peter'},
{name: 'Lex'},
{name: 'Tony'}
]
persons.filter((person, index, arr) => {
return arr.findIndex(auxPerson => auxPerson.name === person.name) === index
})
// output
// 0: {name: 'Peter'} 1: {name: 'Tony'} 2: {name: 'Bruce'} 3: {name: 'Lex'}
Em resumo, da mesma maneira como acontece com o indexOf
, o findIndex
é utilizado para recuperar o índice da primeira ocorrência e comparar para ver se o índice atual equivale a ele, caso o índice seja diferente, isso quer dizer que o valor é duplicado.
4. Mesclando dois ou mais objetos (merge)
Em determinadas ocasiões, precisaremos trabalhar com dados armazenados em origens diferentes, neste caso, objetos diferentes. No exemplo abaixo, segue duas maneiras distintas para se chegar ao mesmo resultado, mesclar objetos em JavaScript.
const person = {
name: 'Peter',
age: 22
}
const contact = {
tel: '22....',
email: 'peter@abc.com'
}
const address = {
country: 'Brazil',
cep: '22020-002'
}
const customer = {...person, ...contact, ...address}
// ou
const customer = Object.assign({}, person, contact, address)
// output
{
"name": "Peter",
"age": 22,
"tel": "22....",
"email": "peter@abc.com",
"country": "Brazil",
"cep": "22020-002"
}
Vale ressaltar, que em termos de performance e também de compatibilidade, o Object.assign
se mostra mais vantajoso. A performance, você pode realizar os testes neste site comparando ambas as operações: https://jsbench.me/.
5. Optional Chaining - Encadeamento opcional
Quando se precisa acessar um valor armazenado em alguns subníveis de um objeto, se não for feito com cuidado, pode causar grandes problemas, ainda mais quando eles são opcionais. Um erro muito comum.
O JavaScript possui varias maneiras de se chegar a um resultado satisfatório de como resolver esse problema, como o uso de if ou operador ternário. Porém, no exemplo abaixo será demonstrado uma maneira mais elegante e clean de fazer esse tratamento.
const person = {
name: 'Peter',
job: {
title: 'Hero',
address: {
city: 'Neighborhood'
}
}
}
// utilizando operador ternário
const city = person && person.job && person.job.address ? person.job.address.city : undefined
// Utilizando optional chaining
const city = person.job?.address?.city
const cep = person.job?.location?.city // undefined
6. Embaralhando um array
Mais uma vez, utilizaremos o sort do JavaScript ao nosso favor. Agora, embaralharemos um array de maneira simples e prática.
const values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
values.sort(() => {
return Math.random() - 0.5;
});
// output 1: (10) [3, 7, 8, 5, 9, 0, 4, 6, 1, 2]
// output 2: (10) [7, 9, 3, 2, 5, 1, 0, 8, 6, 4]
No exemplo anterior, o
se encarrega de retornar com igual probabilidade se o primeiro número é maior que o segundo, ou vice-versa. Math.random() - 0.5
Vale ressaltar que se o embaralhamento (randomização) do array é algo crucial para o funcionamento da aplicação, é recomendado a utilização de um algoritmo mais robusto. Você pode conferir um exemplo utilizando o algoritmo Fisher-Yates neste link: https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array.
7. Nullish coalescing - Operador de coalescência nula
Basicamente, é um operador (??
) que retorna o valor do lado direito quando o valor do lado esquerdo é null
ou undefined
. Seu uso é similar ao do operador OR, ||
, porém ao contrário do operador Nullish, o operador OR retorna o valor do lado direito quando o valor do lado esquerdo é um falsy, ou seja, além dos citados anteriormente, inclui também o valor false, 0, e “” (string vazia)
Veremos agora alguns exemplos comparando ambos
console.log(7 || "não encontrado") // 7
console.log(7 ?? "não encontrado") // 7
console.log(0 || "não encontrado") // "não encontrado"
console.log(0 ?? "não encontrado") // 0
console.log("Peter" || "não encontrado") // "Peter"
console.log("Peter" ?? "não encontrado") // "Peter"
console.log("" || "não encontrado") // "não encontrado"
console.log("" ?? "não encontrado") // ""
console.log(true || "não encontrado") // true
console.log(true ?? "não encontrado") // true
console.log(false || "não encontrado") // "não encontrado"
console.log(false ?? "não encontrado") // false
console.log(undefined || "não encontrado") // "não encontrado"
console.log(undefined ?? "não encontrado") // "não encontrado"
console.log(null || "não encontrado") // "não encontrado"
console.log(null ?? "não encontrado") // "não encontrado"
Em resumo, o operador Nullish considera como valor válido todo aquele que é diferente de null
ou undefined
.
8. Conversão de decimal para outras bases
O método
tem outras utilidades além da convencional. Logo abaixo, exemplos de como transformar um número decimal para binário, hexadecimal e octal. toString
const value = 75 // decimal
value.toString(2)
// output: "1001011"
value.toString(16)
// output: "4b"
value.toString(8)
// output: "113"
9. Operador Rest e Spread
Digamos que seja preciso criar uma função na qual se deseja passar um número indefinido de valores, e essa função deverá retornar o maior valor encontrado. Vejamos como implementar essas funcionalidades utilizando o Rest operator (
). ...
Obs.: Inicialmente pode parecer uma abordagem sem sentido, devido a simplicidade do código interno, mas é uma boa prática centralizar esse tipo de função para reutilização no futuro, e manutenção do algoritmo quando necessário.
Operador Rest
const getMaxNumber = (...values) => {
return values.reduce((max, value) => max > value ? max : value);
}
getMaxNumber(1,4,9,3)
// output: 9
getMaxNumber(1,5,6,3,4,8,7,3)
// output: 8
Operador Spread
Este operador já foi utilizado anteriormente na dica de número 4, Mesclando dois ou mais objetos. Ele nos permite realizar algumas funções interessantes, como mesclar dois objetos, inserir um array dentro de outro array em determinada posição, transformar uma string em array. Sua utilização é bem versátil.
const numbers = [4, 5, 6]
const sequence = [1, 2, 3, ...numbers, 7, 8, 9]
// output (sequence): (9) [1, 2, 3, 4, 5, 6, 7, 8, 9]
const letters = 'abcde'
const array = [...letters]
// output (array): (5) ['a', 'b', 'c', 'd', 'e']
10. Destructuring
Digamos que seja preciso recuperar os primeiros valores de um determinado array numérico, e que seja criado um novo array com o restante dos valores. Faríamos da seguinte maneira utilizando a desestruturação:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
const [first, second, ...rest] = numbers
// output (first): 1
// output (second): 2
// output (rest): (7) [3, 4, 5, 6, 7, 8, 9]
Podemos também extrair determinado valor de um objeto da seguinte maneira:
const person = {firstName: 'Peter', lastName: 'Parker', age: 22}
const {firstName, lastName} = person
// output (firstName): Peter
// output (lastName): Parker
Trocando valores utilizando Destructuring
Esta é simples, apenas para fins didáticos, e demonstração do que podemos fazer com determinadas funcionalidades. Como poderíamos trocar os valores de duas variáveis sem a utilização de uma variável auxiliar? Da seguinte maneira:
let firstName = "Parker"
let lastName = "Peter"
[firstName, lastName ] = [lastName, firstName]
// output (firstName): Peter
// output (lastName): Parker
Estas foram as 10 dicas e truques em JavaScript para aumentar a sua produtividade e eficiência. E não se esqueça da dica extra para comparação de performance, https://jsbench.me/.