Executando verificação de segurança...
1

[Explicação] Functions e Arrow functions no javascript

imagem de abertura

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:

Expressão de função

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()

Resultado function e arrow function

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' ]