[Explicação] Functions e Arrow functions no javascript
Fazia algum tempo que eu não sabia exatamente quais eram as diferenças entre functions e arrow functions, apesar de usar esses dois com uma certa frequência no meu dia a dia.
Resolvi então fazer algumas pesquisas e testes para entender melhor quais são essas diferenças. Estou publicando aqui no TabNews para ajudar a fixar essas informações, para ajudar quem mais possa não conhecer essas diferenças, e também que outros devs possam contribuir com esse conteúdo adicionando mais detalhes sobre as arrow functions.
Sintaxe
A diferença mais notável entre functions e arrow functions certamente é a sintaxe. As arrow functions tendem a ter uma sintaxe mais clean e fácil de ler.
Function
As funções comuns no javascript podem ser declaradas com o uso da palavra-chave function:
function multiplicar(num1, num1) {
return num1 * num2
}
console.log(multiplicar(3, 7)) // 21
Uma função também pode ser declarada como uma função anônima e atribuída a uma variável:
const multiplicar = function (num1, num1) {
return num1 * num2
}
console.log(multiplicar(3, 7)) // 21
Nesse caso, a função em si não tem nome. Portanto, para utilizá-la, a atribuímos a uma constante e a chamamos através dessa constante.
Arrow function
Todas as arrow functions são anônimas, isso significa que é necessário atribuí-las a alguma variável para poder chamá-las:
const multiplicar = (num1, num2) => {
return num1 * num2
}
console.log(multiplicar(3, 7)) // 21
Nos casos em que a arrow function recebe apenas um parâmetro, os parênteses podem ser opcionalmente omitidos:
const calcularDobro = num => {
return num * 2;
}
console.log(calcularDobro(6)); // 12
Se a função possui apenas uma linha de retorno, as chaves também podem ser omitidas:
const calcularIdade = dataNascimento => new Date().getFullYear() - dataNascimento
Lembrando que se a função não tem nenhum parâmetro ou tem mais de um parâmetro, os parênteses dos parâmetros devem ser utilizados novamente:
const obterAnoAtual = () => new Date().getFullYear()
const multiplicar = (num1, num2) => num1 * num2
Funções construtoras
Functions podem ser usadas como construtores quando chamadas usando a palavra-chave new:
function Gato(nome, idade, cor) {
this.nome = nome
this.idade = idade
this.cor = cor
}
const meuGato = new Gato('Dolores', 3, 'cinza')
console.log(meuGato) // Gato { nome: 'Dolores', idade: 3, cor: 'cinza' }
A função acima cria um objeto da mesma forma que seria criado utilizando a seguinte sintaxe:
const meuGato = {
nome: 'Dolores',
idade: 3,
cor: 'Cinza'
}
Já as Arrow functions não podem ser usadas como construtores:
const GatoNormal = function() {};
const GatoArrow = () => {};
const meuGatoNormal = new GatoNormal(); // GatoNormal {}
const meuGatoArrow = new GatoArrow(); // GatoArrow is not a constructor
O contexto de "this"
- Nas
functions
, o valor de this é definido no momento da chamada - Nas
arrow functions
, o valor de this é definino quando são declaradas
Veja o exemplo:
function normalFunction() {
console.log(this);
}
const arrowFunction = () => {
console.log(this);
}
const button = document.querySelector('button');
const obj = {
nome: 'Objeto de teste',
normal: normalFunction,
arrow: arrowFunction
}
obj.normal()
obj.arrow()
Analisando o exemplo, ambas normalFunction
e arrowFunction
são declaradas no escopo global. No momento em que essas duas funções são declaradas, o valor de this em ambos os casos é o contexto global (no caso do navegador, o objeto window
).
Quando essas funções são atribuídas como propriedades do objeto obj
, o valor de this na propriedade normal
passa a ser o próprio objeto obj
, enquando no atributo arrow
o valor de this permanece o mesmo.
Obervação 👀
Note que na definição do objeto, passamos as funções sem os parênteses para passar a referência da função em si. Se passássemos as funções com parênteses (por exemplo,normalFunction()
), os atributos receberiam o retorno da função, que nesse caso seria undefined, uma vez que essas funções não possuem um valor de retorno.
Em outras palavras:
- O valor de this nas
arrow functions
será sempre o mesmo uma vez que declarado. - O valor de this nas
functions
é dinâmico e pode variar de acordo com o contexto de execução em que a função é chamada.
Métodos de objetos
Ao criar um objeto com métodos no JavaScript, é recomendado usar as functions em vez das arrow functions.
Isso porque as functions herdarão o valor de this como sendo o próprio objeto em questão. Já as arrow functions irão herdar o escopo global (geralmente o objeto windows
) como valor do this:
const pessoa = {
nome: 'Pedro Álvares Cabral',
idade: 556,
getNome: function() {
return this.nome
},
getIdade: () => {
return this.idade
}
}
console.log(pessoa.getNome()) // Pedro Álvares Cabral
console.log(pessoa.getIdade()) // undefined
No método getNome
, como ele é declarado como uma function, o valor de this recebe o objeto pessoa
. Já no método getIdade
, como foi declarado como uma arrow function, o valor de this recebe o escopo global. Como o contexto global não possui a propriedade "idade", retorna undefined.
Por esse motivo, o ideal (quase sempre) é usar functions como métodos de objetos.
Métodos de objetos em construtores
Porém, esse comportamento visto anteriormente é diferente quando o método é declarado em um construtor:
function Pessoa(nome, idade) {
this.nome = nome
this.idade = idade
this.getNome = function() {
return this.nome
}
this.getIdade = () => this.idade
}
const pessoa = new Pessoa('Pedro Álvares Cabral', 556)
console.log(pessoa.getNome()) // Pedro Álvares Cabral
console.log(pessoa.getIdade()) // 556
Nesse caso, o construtor Pessoa
força o valor de this como o próprio objeto instanciado com new. Dessa forma, tanto functions como arrow functions retornam o valor esperado normalmente.
Arguments
Toda function disponibiliza dentro do seu escopo o objeto arguments
. Esse objeto é basicamente um array com todos os parâmetros passados para a função.
function imprimirFrutas() {
console.log(arguments)
}
imprimirFrutas('🍓 Morango', '🍍 Abacaxi', '🍇 Uva', '🍉 Melancia')
// [Arguments] { '0': '🍓 Morango', '1': '🍍 Abacaxi', '2': '🍇 Uva', '3': '🍉 Melancia' }
As arrow functions não têm acesso ao objeto arguments. Para obter um resultado semelhante, é necessário usar outros métodos, como o operador rest:
const imprimirFrutas = (...frutas) => {
for (let fruta of arguments) {
console.log(fruta)
}
}
imprimirFrutas('🍓 Morango', '🍍 Abacaxi', '🍇 Uva', '🍉 Melancia')
// [ '🍓 Morango', '🍍 Abacaxi', '🍇 Uva', '🍉 Melancia' ]