Hoje estamos publicando a quarta parte da tradução do manual JavaScript, que é dedicado a funções.
→
Parte 1: primeiro programa, recursos de linguagem, padrões→
Parte 2: estilo do código e estrutura do programa→
Parte 3: variáveis, tipos de dados, expressões, objetos→
Parte 4: recursos→
Parte 5: matrizes e loops→
Parte 6: exceções, ponto e vírgula, literais curinga→
Parte 7: modo estrito, esta palavra-chave, eventos, módulos, cálculos matemáticos→
Parte 8: Visão geral dos recursos do ES6→
Parte 9: Visão geral dos padrões ES7, ES8 e ES9
Funções JavaScript
Vamos falar sobre funções em JavaScript, fazer uma revisão geral e considerar os detalhes sobre elas, cujo conhecimento permitirá que você as use efetivamente.
Uma função é um bloco de código independente que, uma vez declarado, pode ser chamado quantas vezes for necessário. Uma função pode, embora não seja necessária, aceitar parâmetros. Funções retornam um único valor.
Funções em JavaScript são objetos, ou melhor, são objetos do tipo
Function
. Sua principal diferença em relação aos objetos comuns, dando a eles os recursos excepcionais que eles possuem, é que as funções podem ser chamadas.
Além disso, as funções em JavaScript são chamadas de "funções de primeira classe", pois podem ser atribuídas a variáveis, podem ser passadas para outras funções como argumentos e podem ser retornadas de outras funções.
Primeiro, consideramos os recursos do trabalho com funções e as construções sintáticas correspondentes que existiam na linguagem antes do advento do padrão ES6 e ainda são relevantes.
É assim que uma declaração de função se parece.
function doSomething(foo) {
Atualmente, essas funções são chamadas de "normais", distinguindo-as das funções de "seta" exibidas no ES6.
Você pode atribuir uma função a uma variável ou constante. Essa construção é chamada de expressão de função.
const doSomething = function(foo) {
Você pode perceber que no exemplo acima, a função é atribuída a uma constante, mas ela própria não tem um nome. Tais funções são chamadas anônimas. Funções semelhantes podem receber nomes. Nesse caso, estamos falando de uma expressão de função nomeada (expressão de função nomeada).
const doSomething = function doSomFn(foo) {
O uso de tais expressões aumenta a conveniência da depuração (nas mensagens de erro em que o rastreamento da pilha é realizado, o nome da função é visível). O nome da função em uma expressão funcional também pode ser necessário para que a função possa se chamar, o que é indispensável para implementar algoritmos recursivos.
No padrão ES6, apareceram funções de seta, que são especialmente convenientes para usar na forma das chamadas "funções embutidas" - como argumentos passados para outras funções (retornos de chamada).
const doSomething = foo => {
As funções de seta, além de as estruturas usadas para declará-las, serem mais compactas do que as funções comuns, diferem em algumas características importantes, as quais discutiremos a seguir.
Parâmetros de Função
Parâmetros são variáveis definidas no estágio de declaração de uma função e conterão os valores passados para ela (esses valores são chamados de argumentos). As funções no JavaScript podem não ter parâmetros ou ter um ou mais parâmetros.
const doSomething = () => {
Aqui estão alguns exemplos de funções de seta.
Começando com o padrão ES6, as funções podem ter os chamados "parâmetros padrão".
const doSomething = (foo = 1, bar = 'hey') => {
Eles representam valores padrão que são definidos pelos parâmetros de funções se, quando é chamado, os valores de alguns parâmetros não são definidos. Por exemplo, a função mostrada acima pode ser chamada passando os dois parâmetros que recebe e por outros métodos.
doSomething(3) doSomething()
No ES8, tornou-se possível colocar uma vírgula após o último argumento de uma função (isso é chamado de vírgula à direita). Esse recurso permite aumentar a conveniência da edição de código ao usar sistemas de controle de versão durante o desenvolvimento do programa. Detalhes sobre isso podem ser encontrados
aqui e
aqui .
Argumentos passados para funções podem ser representados como matrizes. Para analisar esses argumentos, você pode usar um operador que se parece com três pontos (este é o chamado "operador de extensão" ou "operador de expansão"). Aqui está como fica.
const doSomething = (foo = 1, bar = 'hey') => {
Se as funções precisarem usar muitos parâmetros, pode ser difícil lembrar a ordem de sua sequência. Nesses casos, objetos com parâmetros e oportunidades para a destruição de objetos ES6 são usados.
const doSomething = ({ foo = 1, bar = 'hey' }) => {
Essa técnica permite, descrevendo os parâmetros na forma de propriedades do objeto e passando a função para o objeto, obter o acesso da função aos parâmetros por seus nomes sem usar construções adicionais. Leia mais sobre esta técnica
aqui .
Valores retornados de funções
Todas as funções retornam um determinado valor. Se o comando de retorno não for especificado explicitamente, a função retornará
undefined
.
const doSomething = (foo = 1, bar = 'hey') => {
A execução da função termina após a execução de todo o código que ela contém ou após a palavra-chave de
return
ser encontrada no código. Quando essa palavra-chave é encontrada em uma função, sua operação é concluída e o controle é transferido para o local de onde a função foi chamada.
Se após a palavra-chave
return
você especificar um determinado valor, esse valor retornará ao local da chamada da função como resultado da execução dessa função.
const doSomething = () => { return 'test' } const result = doSomething()
Somente um valor pode ser retornado de uma função. Para poder retornar vários valores, você pode retorná-los como um objeto usando um literal de objeto ou como uma matriz e, ao chamar uma função, use a construção de atribuição destrutiva. Os nomes dos parâmetros são salvos. Ao mesmo tempo, se você precisar trabalhar com um objeto ou uma matriz retornada de uma função, ou seja, na forma de um objeto ou uma matriz, poderá fazê-lo sem atribuição destrutiva.
const doSomething = () => { return ['Roger', 6] } const [ name, age ] = doSomething() console.log(name, age)
A construção
const [ name, age ] = doSomething()
pode ser lida da seguinte maneira: "declare as constantes
name
e
age
e atribua a elas os valores dos elementos da matriz que a função retornará".
Aqui está como a mesma coisa se parece usando um objeto.
const doSomething = () => { return {name: 'Roger', age: 6} } const { name, age } = doSomething() console.log(name, age)
Funções aninhadas
As funções podem ser declaradas dentro de outras funções.
const doSomething = () => { const doSomethingElse = () => {} doSomethingElse() return 'test' } doSomething()
O escopo de uma função aninhada é limitado por uma função externa a ela; não pode ser chamado de fora.
Métodos de objeto
Quando funções são usadas como propriedades de objetos, essas funções são chamadas de métodos de objeto.
const car = { brand: 'Ford', model: 'Fiesta', start: function() { console.log(`Started`) } } car.start()
Esta palavra-chave
Se compararmos a seta e as funções comuns usadas como métodos de objetos, podemos encontrar sua diferença importante, que consiste no significado da palavra
this
chave
this
. Considere um exemplo.
const car = { brand: 'Ford', model: 'Fiesta', start: function() { console.log(`Started ${this.brand} ${this.model}`) }, stop: () => { console.log(`Stopped ${this.brand} ${this.model}`) } } car.start()
Como você pode ver, chamar o método
start()
leva ao resultado esperado, mas o método
stop()
obviamente não funciona corretamente.
Isso se deve ao fato de a palavra-chave this se comportar de maneira diferente quando usada nas funções arrow e ordinárias. Ou seja, a
this
na função de seta contém um link para o contexto que inclui a função. Nesse caso, quando se trata do navegador, esse contexto é o objeto da
window
.
Veja como a execução desse código se parece no console do navegador.
const test = { fn: function() { console.log(this) }, arrFn: () => { console.log(this) } } test.fn() test.arrFn()
Esta palavra-chave é apresentada nas funções convencionais e de setaComo você pode ver, chamar isso em uma função regular significa chamar o objeto, e
this
na função da seta aponta para a
window
.
Tudo isso significa que as funções de seta não são adequadas para a função dos métodos de objeto e construtor (se você tentar usar a função de seta como construtor, um
TypeError
será
TypeError
).
Expressões funcionais chamadas imediatamente
A expressão de função chamada imediatamente (IIFE) é uma função que é automaticamente chamada imediatamente após ser declarada.
;(function () { console.log('executed') })()
O ponto e vírgula antes do IIFE é opcional, mas seu uso permite garantir erros associados ao posicionamento automático de ponto e vírgula.
No exemplo acima, a palavra
executed
irá para o console, após o que o IIFE será encerrado. O IIFE, assim como outras funções, pode retornar os resultados de seu trabalho.
const something = (function () { return 'IIFE' })() console.log(something)
Após executar este exemplo simples, o console obterá a linha
IIFE
, que acabou sendo
something
após a execução da expressão de função chamada imediatamente. Pode parecer que não haja nenhum benefício específico desse projeto. No entanto, se no IIFE forem realizados alguns cálculos complexos que precisam ser feitos apenas uma vez, após o que os mecanismos correspondentes são desnecessários - a utilidade do IIFE é óbvia. Nomeadamente, com essa abordagem, após a execução do IIFE, apenas o resultado retornado pela função estará disponível no programa. Além disso, podemos lembrar que as funções são capazes de retornar outras funções e objetos. Estamos falando de fechamentos, falaremos sobre eles abaixo.
Atualização de recursos
Antes de o código JavaScript ser executado, ele é reorganizado. Já falamos sobre o mecanismo de elevação para variáveis declaradas usando a palavra-chave
var
. Um mecanismo semelhante funciona com funções. Nomeadamente, estamos falando sobre o fato de que declarações de funções durante o processamento do código antes de sua execução serem movidas para a parte superior de seu escopo. Como resultado, por exemplo, você pode chamar a função antes que ela seja declarada.
doSomething()
Se você mover uma chamada de função para que ela apareça após a declaração, nada será alterado.
Se, em uma situação semelhante, uma expressão funcional for usada, um código semelhante gerará um erro.
doSomething()
Nesse caso, verifica-se que, embora a declaração da variável
doSomething
ao topo do escopo, isso não se aplica à operação de atribuição.
Se, em vez de
var
, você usar as palavras-chave
let
ou
const
em uma situação semelhante, esse código também não funcionará, no entanto, o sistema exibirá uma mensagem de erro diferente (
ReferenceError
vez de
TypeError
), porque ao usar
let
e
const
, as declarações de variável e constante não são geradas.
Funções de seta
Agora falaremos mais sobre as funções de seta que já conhecemos. Eles podem ser considerados uma das inovações mais significativas do padrão ES6, diferem das funções comuns, não apenas na aparência, mas também em seu comportamento. Hoje em dia eles são usados extremamente amplamente. Talvez não exista um único projeto moderno em que eles não sejam usados na grande maioria dos casos. Podemos dizer que sua aparência mudou para sempre a aparência do código JS e os recursos de seu trabalho.
De um ponto de vista puramente externo, a sintaxe para declarar funções de seta é mais compacta do que a sintaxe de funções comuns. Aqui está a declaração de uma função regular.
const myFunction = function () {
Aqui está o anúncio da função de seta, que, em geral, se você não levar em consideração os recursos das funções de seta, é semelhante ao anterior.
const myFunction = () => {
Se o corpo de uma função de seta contiver apenas um comando, cujo resultado é retornado, ela poderá ser gravada sem chaves e sem a palavra-chave
return
. Por exemplo, essa função retorna a soma dos argumentos passados para ela.
const myFunction = (a,b) => a + b console.log(myFunction(1,2))
Como você pode ver, os parâmetros das funções das setas, como no caso das funções comuns, são descritos entre parênteses. Além disso, se essa função usar apenas um parâmetro, ela poderá ser especificada sem parênteses. Por exemplo, aqui está uma função que retorna o resultado da divisão do número passado a ele por 2.
const myFunction = a => a / 2 console.log(myFunction(8))
Como resultado, verifica-se que as funções de seta são muito convenientes para uso em situações em que pequenas funções são necessárias.
Return Retorno implícito dos resultados da função
Já abordamos esse recurso das funções de seta, mas é tão importante que ele deve ser discutido em mais detalhes. Estamos falando do fato de que as funções de seta de linha única suportam o retorno implícito dos resultados de seu trabalho. Um exemplo de retorno de um valor primitivo de uma função de seta de linha única que já vimos. E se essa função retornar um objeto? Nesse caso, as chaves do literal do objeto podem confundir o sistema, portanto, parênteses são usados no corpo da função.
const myFunction = () => ({value: 'test'}) const obj = myFunction() console.log(obj.value)
▍ Palavra-chave isto e funções de seta
Acima, quando analisamos os recursos
this
, comparamos as funções regular e de seta. Esta seção pretende chamar sua atenção para a importância de suas diferenças. A
this
, por si só, pode causar certas dificuldades, pois depende do contexto de execução do código e se o modo estrito está ativado ou não.
Como vimos, ao usar a
this
no método de um objeto representado por uma função regular,
this
aponta para o objeto ao qual o método pertence. Nesse caso, falamos sobre vincular a palavra
this
chave
this
a um valor que representa o contexto da função. Em particular, se uma função é chamada como método de objeto, a palavra-chave this é vinculada a esse objeto.
No caso das funções de seta, verifica-se que a ligação
this
não é executada nelas; elas usam a
this
-
this
em seu escopo. Como resultado, eles não são recomendados para uso como métodos de objeto.
O mesmo problema ocorre ao usar funções como manipuladores de eventos para elementos DOM. Por exemplo, o
button
elemento HTML
button
usado para descrever os botões. O evento
click
é
click
quando um usuário clica em um botão. Para responder a esse evento no código, você deve primeiro obter um link para o elemento correspondente e atribuir a ele um manipulador de eventos de
click
como uma função. Como manipulador, você pode usar a função regular e a função de seta. Porém, se no manipulador de eventos você precisar acessar o elemento para o qual é chamado (ou seja, para
this
), a função de seta não funcionará aqui, pois o valor disponível disponível aponta para o objeto de
window
. Para testar isso na prática, crie uma página HTML, cujo código é mostrado abaixo, e clique nos botões.
<!DOCTYPE html> <html> <body> <button id="fn">Function</button> <button id="arrowFn">Arrow function</button> <script> const f = document.getElementById("fn") f.addEventListener('click', function () { alert(this === f) }) const af = document.getElementById("arrowFn") af.addEventListener('click', () => { alert(this === window) }) </script> </body> </html>
Nesse caso, quando você clica nesses botões, janelas contendo
true
serão exibidas. No entanto, no manipulador de eventos
click
do botão com o identificador
fn
, a igualdade
this
o botão é verificada e, no botão com a
arrowFn
identificadoraFn, a igualdade
this
e do objeto da
window
verificada.
Como resultado, se você precisar chamar
this
no manipulador de eventos do elemento HTML, a função seta não funcionará no design de um manipulador desse tipo.
Curto-circuito
Encerramentos são um conceito importante em JavaScript. De fato, se você escreveu funções JS, também usou fechamentos. Os fechamentos são usados em alguns padrões de design - no caso de você precisar organizar um controle rigoroso do acesso a determinados dados ou funções.
Quando uma função é chamada, ela tem acesso a tudo que está no escopo do externo a ela. Mas não há acesso ao que é declarado dentro da função. Ou seja, se uma variável (ou outra função) foi declarada em uma função, elas são inacessíveis ao código externo durante a execução da função ou após a conclusão de seu trabalho. No entanto, se outra função for retornada da função, essa nova função terá acesso a tudo o que foi declarado na função original. Nesse caso, tudo isso será oculto do código externo no fechamento.
Considere um exemplo. Aqui está uma função que pega o nome do cachorro e o exibe no console.
const bark = dog => { const say = `${dog} barked!` ;(() => console.log(say))() } bark(`Roger`)
O valor retornado por esta função ainda não nos interessa; o texto é exibido no console usando IIFE, que neste caso não desempenha um papel especial; no entanto, isso nos ajudará a ver a conexão entre essa função e sua variante, na qual, em vez de chamar uma função que exibe texto para o console, retornaremos essa função da função reescrita
bark()
.
const prepareBark = dog => { const say = `${dog} barked!` return () => console.log(say) } const bark = prepareBark(`Roger`) bark()
O resultado do código em dois casos é o mesmo. Mas no segundo caso, o que foi transferido para a função original quando foi chamado (o nome do cachorro,
Roger
) é armazenado no fechamento, após o qual é usado por outra função retornada do original.
Vamos realizar outro experimento - crie, usando a função original, dois novos para cães diferentes.
const prepareBark = dog => { const say = `${dog} barked!` return () => { console.log(say) } } const rogerBark = prepareBark(`Roger`) const sydBark = prepareBark(`Syd`) rogerBark() sydBark()
Este código produzirá o seguinte.
Roger barked! Syd barked!
Acontece que o valor da constante
say
está vinculado à função retornada da função
prepareBark()
.
Observe que, quando você chama o
prepareBark()
novamente, ele obtém um novo valor, enquanto o valor registrado em,
say
primeira vez que o
prepareBark()
chamado não muda. O ponto é que, a cada chamada para essa função, um novo fechamento é criado.
Sumário
Hoje falamos sobre funções comuns e de seta, sobre os recursos de sua declaração e uso, sobre como
this
palavra
this
chave se comporta em diferentes situações e sobre fechamentos. Da próxima vez, discutiremos matrizes e loops.
Caros leitores! Como você se sente sobre as funções de seta no JavaScript?
