JavaScript: Tudo sobre arrow functions

Apesar de ser uma feature bastante usada hoje em dia, algumas pessoas ainda não conhecem alguns segredos dessa nova sintaxe de funções em JavaScript.
Quer saber tudo o que as arrow functions são capazes de fazer? Vem comigo que eu te mostro =)

Nova sintaxe

Pra quem ainda não teve contato com as arrow functions, Vamos começar entendendo a nova sintaxe.

1
2
3
const sum = (a, b) => {
return a + b
}

À primeira vista pode parecer estranho, mas usando function tradicional, podemos obter o mesmo resultado assim:

1
2
3
const sum = function (a, b) {
return a + b
}

Acho que assim fica mais fácil de entender =)

A criação de uma arrow function consistem em 3 “passos”:

  • Os parênteses, que é por onde a função recebe os argumentos (assim como na function tradicional);
  • A “seta” => - responsável pelo nome “arrow” function;
  • E as chaves: o bloco de código que representa o corpo da função.

Perceba que a ideia é bastante similar à forma padrão de usar funções em JS, com a diferença que não usamos a palavra chave function, e sim uma seta (=>) entre os parênteses (argumentos) e as chaves (bloco que gera o corpo da função).

Perceba também que nós usamos a arrow function como uma expressão - no caso, atribuindo o resultado da criação da arrow function à uma variável.

Diferente das function tradicionais, não existe uma forma literal de escrever uma arrow- function. Ela sempre deve ser usada como uma expressão.

Também não é possível nomear uma arrow function. Elas são sempre anônimas.

E para executar a função, nada muda:

1
console.log(sum(1, 2)) // 3

Variações da sintaxe

Arrow functions permitem algumas variações, para deixar o código ainda mais enxuto. Vamos ver alguns exemplos:

1) Função que não tem nada no seu corpo além do retorno

Se a função vai apenas retornar algum valor, nós podemos omitir as chaves (corpo da função) e a palavra chave return.

Ao invés de escrever:

1
2
3
const sum = (a, b) => {
return a + b
}

Podemos apenas escrever:

1
2
const sum = (a, b) =>
a + b

O resultado é o mesmo! E não há problema algum em quebrar linha após a “seta”, pois diferente do return, aqui não se aplica o ASI (Automatic Semicolon Insertion, ou Inserção Automática do Ponto-e-Vírgula).

Pra deixa ainda mais simples, podemos colocar tudo em uma linha só:

1
const sum = (a, b) => a + b

Para esse exemplo, o retorno da função fica implícito. Tudo o que está sendo colocado logo após a “seta” será considerado o retorno da função =)

ATENÇÃO

No exemplo acima, nós estamos retornando apenas a soma de dois números. Essa sintaxe é válida para qualquer tipo de dado sendo retornado exceto para objetos.

Por que objetos são uma exceção?

Para criarmos objetos literais, nós usamos as chaves. Logo, ao retornar um objeto, a criação da função teria um sentido ambíguo: as chaves são para abrir o bloco do corpo da função, ou é um retorno de objeto literal?

Para esse caso específico, sempre que quisermos retornar um objeto de forma implítica usando uma arrow function, nós precisamos envolver esse objeto em parênteses.

Vamos ver alguns exemplos.

Para retornar um objeto de forma explícita (com o corpo da função, e o return), nós faríamos assim:

1
2
3
4
5
6
7
const person = () => {
return {
name: 'John',
surname: 'Doe',
age: 30
}
}

Agora, se retornar de forma implícita, teríamos que fazer da seguinte forma:

1
2
3
4
5
const person = () => ({
name: 'John',
surname: 'Doe',
age: 30
})

Veja que a diferença foi apenas envolver o objeto em parênteses.

Em JS, os parênteses servem apenas para dar prioridade na execução de alguma expressão, então basicamente o que acontece é que toda a expressão dentro dos parênteses é que será retornada pela função person, assim que ela for executada. Fique muito atenta(o) a isso =)

2) Funções com apenas um argumento

Quando a função recebe apenas um argumento, podemos omitir os parênteses.

Então essa função:

1
2
3
const plusOne = (value) => {
return value + 1
}

Poderia ser escrita com retorno implícito:

1
const plusOne = (value) => value + 1

E como só um argumento é esperado pela função, podemos omitir os parênteses:

1
const plusOne = value => value + 1

Importante: Não é obrigatório omitir os parênteses. Eu, particularmente, prefiro deixar, pois acho que torna a leitura mais fácil. Mas é uma possibilidade. Com o tempo pode ser que você se acostume, então use da forma que achar melhor. Apenas siga um padrão do início ao fim do seu projeto =)

Posso substituir todas as “function” por “arrow function”?

Essa é uma ótima pergunta, e muitas pessoas não a fazem, apenas substituem e vida que segue.

Mas não é bem assim!

As arrow functions não foram criadas apenas para ser uma sintaxe mais exuta e substituir as function tradicionais. Elas têm alguns detalhes importantes que é preciso levar em consideração antes de saber se devemos ou não só sair substituindo tudo.

Escopo léxico

Funções, ao serem criadas, geram um escopo próprio. E esse escopo só existe enquanto a função existe.

No JavaScript, temos a palavra chave this, que é bastante polêmica, pois na maioria das linguagens, essa palavra chave faz referência à um objeto criado à partir de um construtor, quando usada dentro de uma classe; ou quando usada dentro de um método de objeto, ela faz referência à esse objeto.

Mas isso nem sempre é verdade: é possível “injetar” o this, dependendo da forma que você executa uma função.

Se você não está por dentro desse assunto, recomendo esse post que escrevi a um tempo atrás. Leia o artigo, e depois volte aqui que tudo vai fazer sentido. Pode ir. Eu espero =)

Já leu? Ótimo, vamos continuar! :D

O importante aqui é saber que, diferente das function tradicionais, você não consegue “injetar” o this em arrow functions. Isso mesmo!

Se o this for usado dentro de uma arrow function, esse this vai fazer referência ao objeto que ele já era referência no momento da criação da arrow function.

Ficou complicado de entender? Vamos ver alguns exemplos!

1
2
3
4
5
function getThis () {
return this
}

console.log(getThis()) // Objeto window

Se executarmos a função getThis, criada no escopo global, em algum navegador, vamos ter como retorno o objeto window, pois quando uma função que têm o this dentro não for uma função construtora, nem um método de objeto, o this vai ser referência ao objeto global.

Mas se usarmos 'use strict', como você já deve saber, essa diretiva faz com que o this global seja undefined. Logo, esse é o resultado quando estamos executando nossa função em strict mode:

1
2
3
4
5
6
7
'use strict'

function getThis () {
return this
}

console.log(getThis()) // undefined

E a questão interessante ao usar arrow function é exatamente essa: nos dois casos, a arrow function vai retornar o objeto window, por causa do escopo léxico. Lembre-se: o this dentro de uma arrow function sempre vai ser referência ao objeto que ele já era referência no momento da criação da arrow function. (nesse caso, o objeto window):

1
2
3
4
5
const getThis = () => {
return this
}

console.log(getThis()) // Objeto window

E usando 'use strict':

1
2
3
4
5
6
7
'use strict'

const getThis = () => {
return this
}

console.log(getThis()) // Objeto window

Uma outra situação bastante comum é ao usar um método de objeto:

1
2
3
4
5
6
7
8
9
const counter = {
value: 0,
increment: function () {
return ++this.value
}
}

console.log(counter.increment()) // 1
console.log(counter.increment()) // 2

Agora, ao tentar usar uma arrow function como método do objeto, olhe o que acontece:

1
2
3
4
5
6
7
8
const counter = {
value: 0,
increment: () => {
return ++this.value
}
}

console.log(counter.increment()) // NaN

Veja que o valor retornado pelo método increment agora é NaN. E isso acontece porque o objeto está sendo criado no escopo global, logo, o método increment também está sendo criado nesse escopo, já que o objeto counter não está dentro de nenhuma função.

E outra coisa importante a notar é o valor de value:

1
console.log(counter.value) // 0

Veja que o valor de value não foi alterado! Logo, o value que está sendo exibido é um valor que foi criado no objeto global window, e para esse value é que foi atribuído o valor NaN:

1
console.log(window.value) // NaN

Com isso, fica claro que o this dentro de uma arrow function vai ser sempre referência ao objeto ao qual ele já era, no momento em que a função foi criada.

Essa regra vale para qualquer chamada de função que precise usar o this dentro, então preste atenção, ok? =)

Só mais um exemplo, apenas para complementar: eventos!

O método addEventListener sempre injeta o this dentro da função usada como listener desse evento.

Veja esse exemplo:

1
2
3
4
const $input = document.querySelector('input[type="text"]')
$input.addEventListener('input', function () {
console.log('value:', this.value)
}, false)

No exemplo acima, estou assumindo que o código vai ser executado em um ambiente onde existe um campo input do tipo text.

Ao começar a digitar algo dentro desse campo, podemos ver o valor do campo sendo exibido no console, pois o this dentro da função passada como listener do evento é injetado pelo addEventListener, fazendo referência ao objeto do DOM ao qual o evento foi atrelado.

Agora tente usar uma arrow function no lugar da function tradicional:

1
2
3
4
const $input = document.querySelector('input[type="text"]')
$input.addEventListener('input', () => {
console.log('value:', this.value)
}, false)

Nesse caso, veja que o valor exibido no cosole é sempre undefined (a menos que exista um objeto no escopo em que a função foi criada, e esse objeto tenha uma propriedade value).

Considerações

Com tudo isso em mente, podemos considerar alguns pontos:

  • se a função que você tem não depende do valor de algum this, você pode substituí-la por arrow function sem problemas;
  • evite usar o this. No caso do exemplo do evento, toda função listener de um evento recebe um objeto de evento, com uma propriedade target, que faz referência ao elemento que recebeu o evento. Use esse objeto se precisar manipular ou fazer qulquer coisa com o elemento que disparou o evento, ao invés de usar this. Dessa forma você evita os problemas vistos acima.

Parabéns! Agora você já sabe como usar corretamente as arrow functions sem problemas!

Já conhecia essa nova sintaxe de funções? Ficou com alguma dúvida? Faltou falar alguma coisa no post? Gostaria de sugerir um assunto para os próximos posts?

Deixe nos comentários! Ficarei muito feliz em ler e te responder :D

Até a próxima :D