Em 2015, com o lançamento do ES6, muitas mudanças começaram a acontecer no JavaScript. Quais foram as mudanças abaixo?
Hoje em dia, funções possem ser definidas com valores padrões de forma muito mais fácil graças a implementação dos "default parameters".
A declaração desses parâmetros é feita no próprio campo de parâmetros,com uma atribuição realizada na hora da declaração, como visto a seguir:
// Sintaxe básica
function multiply(a, b = 2) {
return a * b;
}
multiply(5); // 10
// Default parameters também funcionam com "later default parameters", ou seja, parâmetros que são lidos depois (mais para direita)
function foo (num = 1, multi = multiply(num)) {
return [num, multi];
}
foo(); // [1, 2]
foo(6); // [6, 12]
Mas, para exemplificar como essa mudança impactou a criação de funções com parâmetros padrões, vamos ver o seguinte exemplo:
// Queremos um elemento <p>, com um conteúdo definido e 2 classes atribuídas. Para isso, utilizaremos a função
createElement('p', {
content: 'Um texto super único',
classNames: ['texto-muito-especial', 'muito-grande']
});
// Retorna <p class="texto-muito-especial muito-grande">Um texto super único</p>
// Para esse método ser mais útil, ele deve sempre retornar um elemento padrão quando qualquer um dos argumentos não for passado.
createElement();
// Retorna <div class="texto-modulo padrao">Padrãozinho</div>
Antes de existirem os default parameters, uma possível implementação dessa função era a seguinte:
// Sem os default parameters, fica desnecessariamente grande
function createElement (tag, config) {
tag = tag || 'div';
config = config || {};
const element = document.createElement(tag);
const content = config.content || 'Padrãozinho';
const text = document.createTextNode(content);
let classNames = config.classNames;
if (classNames === undefined) {
classNames = ['modulo-texto', 'padrao'];
}
element.classList.add(...classNames);
element.appendChild(text);
return element;
}
E assim fica a mesma função com os default parameters:
function createElement (tag = 'div', {
content = 'Padrãozinho',
classNames = ['modulo-texto', 'especial']
} = {}) {
const element = document.createElement(tag);
const text = document.createTextNode(content);
element.classList.add(...classNames);
element.appendChild(text);
return element;
}
Sabe aquela ideia de de ter que digitar console.log("Oi" + nome +
"! Como você está?") utilizando o símbolo "+" para concactenar os argumentos
passados?
O ES6 resolveu muito esse problema, trazendo as template strings pro Javascript.
A sintaxe das template strings é a seguinte:
`string text`
Para gerar uma string de múltiplas linhas:
`string text line 1
string text line 2`
Interpolando valores diretamente dentro da string:
`string text ${expression} string text`
Templates reutilizáveis:
const templateFn = expression => `string text ${expression} string text`;
Porém, ainda existe uma outra feature bacana das template strings, que são chamadas de "tagged template literals", que tem a capacidade de chamar funções (chamadas de tag functions). Veremos a seguir:
Vamos supor a função mensagemExemplo():
function mensagemExemplo(mensagemParte1, nome, mensagemParte2) {
console.log(`${mensagemParte1} ${nome}! ${mensagemParte2}`)
}
Ela poderia ser chamada em modo de tagged functions utilizando as template strings na seguinte sintaxe:
function exemploMelhorado(strings, ...expressoes) {
return expressoes.reduce((acumulator, expressao, index) => {
return acumulator + expressao + strings[index + 1]
}, strings[0])}
Executando a função:
nome = "Caio"
let mensagemMelhoradaFinal = exemploMelhorado`Olá ${nome}! Tudo bem?`
console.log(mensagemMelhoradaFinal)
E como isso acontece? Vamos modificar a função e pedir pra ele retornar apenas os argumentos (que são arrays):
Resultado: é claro que parece que mais trabalhoso o exemplo melhorado, mas imagina o caso de uma função com muitos argumentos? Sem falar na flexibilidade dada ao código, já que agora é possível manipular os itens passados da forma que quiser.
Destructring é a técnica implementada no ES6 que possibilita a extração de dados de arrays ou objetos em diversas variáveis.
É possível, por exemplo, declarar arrays e objetos utilizando o rest operator da seguinte maneira:
let a, b, rest;
[a, b] = [10, 20];
console.log(a); // 10
console.log(b); // 20
[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(a); // 10
console.log(b); // 20
console.log(rest); // [30, 40, 50]
({ a, b } = { a: 10, b: 20 });
console.log(a); // 10
console.log(b); // 20
// Stage 4(finished) proposal
({a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40});
console.log(a); // 10
console.log(b); // 20
console.log(rest); // {c: 30, d: 40}
Repare que é possível desestruturar (destructure) o array para os elementos do lado esquerdo da atribuição.
Outro exemplo que mostra bem o que acontece:
const x = [1, 2, 3, 4, 5];
const [y, z] = x;
console.log(y); // 1
console.log(z); // 2
Porém é necessário ficar atento ao fato de que, se o elemento passado na atribuição do lado direito tiver seu comprimento (length) menor que o elemento do lado esqurdo, apenas serão passados os valores existentes, enquanto os outros valores serão redefinidos para "undefined":
const foo = ['one', 'two'];
const [red, yellow, green, blue] = foo;
console.log(red); // "one"
console.log(yellow); // "two"
console.log(green); // undefined
console.log(blue); //undefined
Também é possível trocar variáveis de lugar sem precisar de uma temporária!
let a = 1;
let b = 3;
[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1
A forma mais fácil de observar destructuring básico de objetos pode ser vista no seguinte exemplo, que funciona de forma parecida com o Array:
const user = {
id: 42,
isVerified: true
};
const {id, isVerified} = user;
console.log(id); // 42
console.log(isVerified); // true
Porém, uma das funcionalidades mais interessantes do destructuring em objetos é a possibilidade de deixar o código mais enxuto e ainda assim, bastante legível (talvez até mais). Veremos o exemplo.
Considere o seguinte objeto com várias informações sobre o usuário.
const user = {
id: 42,
displayName: 'lemoscaio',
fullName: {
firstName: 'Caio',
lastName: 'Lemos'
}
};
Sem a possibilidade de object destructuring, caso precisássemos de uma função que retorna o "id" do usuário, precisamos de algo como:
function userId(user) {
return user.id;
}
console.log(userId(user)); // 42
Porém, a partir do ES6, é possível trabalhar com a seguinte estrutura, deixando implícito o objeto na função:
function userId({id}) {
return id;
}
console.log(userId(user)); // 42
Também é possível atribuir nomes para as propriedades, que funcionam como se fossem variáveis dentro da função, usar destructuring em objetos aninhados e até mesmo utilizar a funcionalidade para declarar default parameters, como visto no próprio subtópico.