ES6 Funções das setas: Fat and Concise Syntax in JavaScript

Neste artigo, você aprenderá tudo sobre a sintaxe da função de seta do JavaScript – incluindo algumas das gotchas de que você precisa estar ciente ao alavancar as funções de seta no seu código. Você verá muitos exemplos que ilustram como eles funcionam.

As funções de seta foram introduzidas no JavaScript com o lançamento do ECMAScript 2015, também conhecido como ES6. A sua sintaxe concisa, e a forma como lidam com esta palavra-chave, estão entre as principais características que contribuíram para o considerável sucesso das funções de seta entre os desenvolvedores.

Tornar uma Função Pre-ES6 numa Função de Seta

Pode considerar as funções como uma espécie de receita onde guarda instruções úteis para realizar algo que precisa de ser feito no seu programa, como executar uma acção ou devolver um valor. Ao chamar sua função, você executa as etapas incluídas em sua receita, e pode fazê-lo toda vez que chamar essa função sem precisar reescrever a receita de novo e de novo.

Aqui está uma forma padrão de declarar uma função e depois chamá-la em JavaScript:

// function declarationfunction sayHiStranger() { return 'Hi, stranger!'}// call the functionsayHiStranger()

Pode também escrever a mesma função como expressão de uma função, assim:

const sayHiStranger = function () { return 'Hi, stranger!'}

As funções de seta são sempre expressões. Aqui está como você poderia reescrever a função acima usando a notação de seta gorda:

const sayHiStranger = () => 'Hi, stranger'

Os benefícios disto incluem:

  • apenas uma linha de código
  • não function palavra-chave
  • não return palavra-chave
  • sem suportes encaracolados {}

Em JavaScript, as funções são “cidadãos de primeira classe”. Ou seja, você pode armazenar funções em variáveis, passá-las para outras funções como argumentos, e devolvê-las de outras funções como valores. Você pode fazer tudo isso usando as funções de seta.

Vamos percorrer as várias maneiras como você pode escrever funções de seta.

Sintaxe Sem Parentes

No exemplo acima, a função não tem parâmetros. Neste caso, você deve adicionar um conjunto de parênteses vazios () antes do símbolo da seta de gordura (=>). O mesmo se aplica quando se tem mais do que um parâmetro:

const getNetflixSeries = (seriesName, releaseDate) => `The ${seriesName} series was released in ${releaseDate}`// call the functionconsole.log(getNetflixSeries('Bridgerton', '2020') )// output: The Bridgerton series was released in 2020

Com apenas um parâmetro, no entanto, pode ir em frente e deixar de fora os parênteses (não é necessário, mas pode):

const favoriteSeries = seriesName => seriesName === "Bridgerton" ? "Let's watch it" : "Let's go out"// call the functionconsole.log(favoriteSeries("Bridgerton"))// output: "Let's watch it"

Tenham cuidado, no entanto. Se, por exemplo, você decidir usar um parâmetro padrão, você deve embrulhá-lo dentro de parênteses:

// with parentheses: correctconst bestNetflixSeries = (seriesName = "Bridgerton") => `${seriesName} is the best`// outputs: "Bridgerton is the best"console.log(bestNetflixSeries())// no parentheses: errorconst bestNetflixSeries = seriesName = "Bridgerton" => `${seriesName} is the best`// Uncaught SyntaxError: invalid arrow-function arguments (parentheses around the arrow-function may help)

E só porque você pode, não significa que você deve. Misturado com um pouco de sarcasmo leve e bem intencionado, Kyle Simpson (do You Don’t Know JS fame) colocou seus pensamentos em omitir parênteses neste fluxograma.

Implicit Return

Quando você tem apenas uma expressão em seu corpo funcional, você pode manter tudo em uma linha, remover o aparelho encaracolado, e eliminar com a palavra-chave return. Você acabou de ver como essas linhagens de uma linha funcionam nos exemplos acima. Aqui está mais um exemplo, apenas para uma boa medida. A função orderByLikes() faz o que diz na lata: ou seja, retorna um array de objetos da série Netflix ordenados pelo maior número de likes:

// using the JS sort() function to sort the titles in descending order // according to the number of likes (more likes at the top, fewer at the bottomconst orderByLikes = netflixSeries.sort( (a, b) => b.likes - a.likes )// call the function // output:the titles and the n. of likes in descending orderconsole.log(orderByLikes)

Isso é legal, mas fique de olho na legibilidade do seu código – especialmente ao sequenciar um monte de funções de seta usando one-liners e a sintaxe sem parênteses, como neste exemplo:

const greeter = greeting => name => `${greeting}, ${name}!`

O que está acontecendo lá? Tente usar a sintaxe da função regular:

function greeter(greeting) { return function(name) { return `${greeting}, ${name}!` }} 

Agora, você pode ver rapidamente como a função externa greeter tem um parâmetro, greeting, e retorna uma função anônima. Esta função interna por sua vez tem um parâmetro chamado name e retorna uma string usando o valor de ambos greeting e name. Aqui está como você pode chamar a função:

const myGreet = greeter('Good morning')console.log( myGreet('Mary') ) // output: "Good morning, Mary!" 

Watch Out for these Implicit Return Gotchas

Quando a sua função de seta contém mais do que uma declaração, precisa de as embrulhar todas em chaves encaracoladas e usar a palavra-chave return. No código abaixo, a função constrói um objeto contendo o título e resumo de algumas séries Netflix (as revisões Netflix são do site Rotten Tomatoes) :

const seriesList = netflixSeries.map( series => { const container = {} container.title = series.name container.summary = series.summary // explicit return return container} )

A função de seta dentro da função .map() desenvolve-se sobre uma série de declarações, no final das quais retorna um objeto. Isto torna inevitável o uso de suportes encaracolados ao redor do corpo da função. Além disso, como você está usando chaves encaracoladas, um retorno implícito não é uma opção. Você deve usar a palavra-chave return.

Se a sua função seta retorna um objeto literalmente usando o retorno implícito, você precisa envolver o objeto dentro de parênteses redondos. Não fazer isso resultará em um erro, porque o mecanismo JS analisa o objeto literalmente entre parênteses curvos como os parênteses curvos da função. E, como você acabou de notar acima, quando você usa chaves encaracoladas em uma função de seta, você não pode omitir a palavra-chave return.

Esta sintaxe é demonstrada na versão mais curta do código anterior:

// Uncaught SyntaxError: unexpected token: ':'const seriesList = netflixSeries.map(series => { title: series.name });// Works fineconst seriesList = netflixSeries.map(series => ({ title: series.name }));

Não se pode nomear funções de seta

Funções que não têm um identificador de nome entre a palavra-chave function e a lista de parâmetros são chamadas funções anónimas. Aqui está o aspecto de uma expressão regular de funções anónimas:

const anonymous = function() { return 'You can\'t identify me!' }

As funções de seta são todas funções anónimas:

const anonymousArrowFunc = () => 'You can\'t identify me!' 

As de ES6, variáveis e métodos podem inferir o nome de uma função anónima a partir da sua posição sintáctica, usando a sua propriedade name. Isto torna possível identificar a função ao inspecionar seu valor ou reportar um erro.

Cheque isto usando a propriedade anonymousArrowFunc:

console.log(anonymousArrowFunc.name)// output: "anonymousArrowFunc"

Mas saiba que esta propriedade inferida name só existe quando a função anônima é atribuída a uma variável, como nos exemplos acima. Se você usar uma função anônima como uma chamada de retorno, por exemplo, você perde esta útil funcionalidade. Isto é exemplificado na demonstração abaixo onde a função anônima dentro do método .setInterval() não pode se valer da propriedade name:

let counter = 5let countDown = setInterval(() => { console.log(counter) counter-- if (counter === 0) { console.log("I have no name!!") clearInterval(countDown) }}, 1000)

E isso não é tudo. Isto inferido name propriedade ainda não funciona como um identificador adequado que você pode usar para se referir à função a partir de dentro de si mesmo – como por exemplo, para recursividade, eventos de desvinculação, etc.

O anonimato intrínseco das funções de seta levou Kyle Simpson a expressar a sua visão sobre as funções de seta da seguinte forma:

Desde que eu não acho que funções anónimas sejam uma boa ideia para usar frequentemente nos seus programas, não sou fã de usar o formulário de funções de setas =>. – Você Não Sabe JS

Como as Funções de Seta Manejam a Palavra-Chave

A coisa mais importante a lembrar sobre as funções de seta é a forma como lidam com a this palavra-chave. Em particular, a this palavra-chave dentro de uma função de seta não tem ressalto.

Para ilustrar o que isto significa, veja a demonstração abaixo:

Veja a caneta
JS isto em funções de seta por SitePoint (@SitePoint)
em CodePen.

Aqui está um botão. Clicando no botão aciona um contador reverso de 5 a 1. Os números são exibidos no próprio botão:

<button class="start-btn">Start Counter</button>...const startBtn = document.querySelector(".start-btn");startBtn.addEventListener('click', function() { this.classList.add('counting') let counter = 5; const timer = setInterval(() => { this.textContent = counter counter -- if(counter < 0) { this.textContent = 'THE END!' this.classList.remove('counting') clearInterval(timer) } }, 1000) })

Notem como o manipulador de eventos dentro do método .addEventListener() é uma expressão regular de função anónima, não uma função de seta. Porquê? Se você logar this dentro da função, você verá que ela referencia o elemento do botão ao qual o ouvinte foi anexado, que é exatamente o que é esperado e o que é necessário para que o programa funcione como planejado:

startBtn.addEventListener('click', function() { console.log(this) ...})

Aqui é o que parece na consola de ferramentas de desenvolvimento do Firefox:

No entanto, tente substituir a função regular por uma função de seta, como esta:

startBtn.addEventListener('click', () => { console.log(this) ...})

Agora, this não faz mais referência ao botão. Em vez disso, ele referencia o objeto Window:

Isto significa que, se você quiser usar this para adicionar uma classe ao botão depois de clicado, por exemplo, o seu código não funcionará:

// change button's border's appearancethis.classList.add('counting')

Aqui está a mensagem de erro na consola:

Isto acontece porque, quando você usa uma função de seta, o valor da palavra-chave this não é ressaltado, mas é herdado do escopo dos pais (isto é chamado de escopo léxico). Neste caso particular, a função de seta em questão está sendo passada como argumento para o método startBtn.addEventListener(), que está no escopo global. Consequentemente, o this dentro do manipulador da função seta também está vinculado ao escopo global – ou seja, ao objeto Window.

Então, se você quiser this para referenciar o botão iniciar no programa, a abordagem correta é usar uma função regular, não uma função de seta.

A próxima coisa a notar na demonstração acima é o código dentro do método .setInterval(). Aqui, também, você encontrará uma função anônima, mas desta vez é uma função de seta. Porquê?

Note qual seria o valor de this se você usasse uma função normal:

const timer = setInterval(function() { console.log(this) ...}, 1000)

Será o elemento button? De forma alguma. Seria o objeto Window!

De facto, o contexto mudou, uma vez que this está agora dentro de uma função sem limites ou global que está a ser passada como um argumento para .setInterval(). Portanto, o valor da palavra-chave this também mudou, uma vez que agora está vinculado ao escopo global. Um hack comum nesta situação tem sido o de incluir outra variável para armazenar o valor da palavra-chave this para que ela continue se referindo ao elemento esperado – neste caso, a button element:

const that = thisconst timer = setInterval(function() { console.log(that) ...}, 1000)

Você também pode usar .bind() para resolver o problema:

const timer = setInterval(function() { console.log(this) ...}.bind(this), 1000)

Com funções de seta, o problema desaparece completamente. Aqui está o valor de this quando você usa uma função de seta:

const timer = setInterval( () => { console.log(this) ...}, 1000)

Desta vez, a consola regista o botão, que é exactamente o que é necessário. De facto, o programa vai alterar o texto do botão, pelo que necessita de this para se referir ao elemento button> elemento:

const timer = setInterval( () => { console.log(this) // the button's text displays the timer value this.textContent = counter}, 1000)

As funções de seta não têm o seu próprio contexto this. Eles herdam o valor de this do pai, e é por causa desta característica que eles fazem uma grande escolha em situações como a acima.

As funções de seta não são sempre a ferramenta certa para o trabalho

As funções de seta não são apenas uma nova forma elegante de escrever funções em JavaScript. Elas têm suas próprias peculiaridades e limitações, o que significa que há casos em que você não quer usar uma função de seta. O manipulador de cliques na demonstração anterior é um caso em ponto, mas não é o único. Vamos examinar mais alguns.

Arrow Functions as Object Methods

Arrow functions don’t work well as methods on objects. Aqui está um exemplo. Considere este objeto netflixSeries, que tem algumas propriedades e um par de métodos. Chamando console.log(netflixSeries.getLikes()) deve imprimir uma mensagem com o número atual de likes, e chamando console.log(netflixSeries.addLike()) deve aumentar o número de likes em um e então imprimir o novo valor com uma mensagem de agradecimento no console:

const netflixSeries = { title: 'After Life', firstRealease: 2019, likes: 5, getLikes: () => `${this.title} has ${this.likes} likes`, addLike: () => { this.likes++ return `Thank you for liking ${this.title}, which now has ${this.likes} likes` } }

Em vez disso, chamando o método .getLikes() retorna “undefined has NaN likes”, e chamando o método .addLike() retorna “Thank you for liking undefined, which now has NaN likes”. Então, this.title e this.likes não fazem referência às propriedades do objeto title e likes respectivamente.

Após novamente, o problema é o escopo lexical das funções das setas. O this dentro do método do objeto está referenciando o escopo do pai, que neste caso é o objeto Window, não o próprio pai – ou seja, não o objeto netflixSeries.

A solução, é claro, é usar uma função regular:

const netflixSeries = { title: 'After Life', firstRealease: 2019, likes: 5, getLikes() { return `${this.title} has ${this.likes} likes` }, addLike() { this.likes++ return `Thank you for liking ${this.title}, which now has ${this.likes} likes` } }// call the methods console.log(netflixSeries.getLikes())console.log(netflixSeries.addLike())// output: After Life has 5 likesThank you for liking After Life, which now has 6 likes

Funções de seta com bibliotecas de terceiros

Outra coisa que tem que se saber é que as bibliotecas de terceiros muitas vezes ligam chamadas de método para que o valor this aponte para algo útil.

Por exemplo, dentro de um manipulador de eventos jQuery, this irá dar-lhe acesso ao elemento DOM ao qual o manipulador estava ligado:

$('body').on('click', function() { console.log(this)})// <body>

Mas se usarmos uma função de seta – que, como vimos, não tem o seu próprio contexto this – obtemos resultados inesperados:

$('body').on('click', () =>{ console.log(this)})// Window

Aqui está mais um exemplo usando Vue.js:

new Vue({ el: app, data: { message: 'Hello, World!' }, created: function() { console.log(this.message); }})// Hello, World!

Inside the created hook, this is bound to the Vue instance, so the “Hello, World!” message is displayed.

Se usarmos uma função de seta, no entanto, this apontará para o escopo pai, que não tem uma message propriedade:

new Vue({ el: app, data: { message: 'Hello, World!' }, created: function() { console.log(this.message); }})// undefined

Funções de seta Sem argumentos Object

Por vezes, você pode precisar criar uma função com um número indefinido de parâmetros. Por exemplo, digamos que você queira criar uma função que lista sua série preferida de Netflix ordenada por preferência. No entanto, você ainda não sabe quantas séries você vai incluir. O JavaScript disponibiliza o objeto argumentos, um objeto tipo arrays (não um array completo, no entanto), que armazena os valores que são passados para a função quando ela está sendo chamada.

Tente implementar esta funcionalidade usando uma função de seta:

const listYourFavNetflixSeries = () => { // we need to turn the arguments into a real array // so we can use .map() const favSeries = Array.from(arguments) return favSeries.map( (series, i) => { return `${series} is my #${i +1} favorite Netflix series` } ) console.log(arguments)}console.log(listYourFavNetflixSeries('Bridgerton', 'Ozark', 'After Life')) 

Quando você chama a função, você receberá a seguinte mensagem de erro: Uncaught ReferenceError: arguments is not defined. Isto significa que o objecto arguments não está disponível dentro das funções de setas. Na verdade, substituir a função de seta por uma função normal faz o truque:

const listYourFavNetflixSeries = function() { const favSeries = Array.from(arguments) return favSeries.map( (series, i) => { return `${series} is my #${i +1} favorite Netflix series` } ) console.log(arguments) }console.log(listYourFavNetflixSeries('Bridgerton', 'Ozark', 'After Life'))// output: 

Então, se precisar do objecto arguments, não pode usar funções de seta.

Mas e se quiser realmente usar uma função de seta para replicar a mesma funcionalidade? Uma coisa que você pode fazer é usar os parâmetros de descanso do ES6 (...). Aqui está como você poderia reescrever sua função:

>

const listYourFavNetflixSeries = (...seriesList) => { return seriesList.map( (series, i) => { return `${series} is my #${i +1} favorite Netflix series` } ) }

Conclusion

Utilizando as funções de setas, pode escrever linhas únicas concisas com retorno implícito e finalmente esquecer os hacks antigos para resolver a ligação da palavra-chave this em JavaScript. Funções de setas também funcionam muito bem com métodos de array como .map(), .sort(), .forEach(), .filter(), e .reduce(). Mas lembre-se, as funções de seta não são um substituto para funções JS normais, e não podem ser usadas para tudo.

Se você tiver alguma dúvida sobre funções de setas, ou precisar de ajuda para acertá-las, eu recomendo que você pare nos fóruns amigáveis do SitePoint, onde há muitos programadores com conhecimentos prontos para ajudar.

Deixe uma resposta

O seu endereço de email não será publicado.