Como já sabemos, uma função em JavaScript é um valor.
Cada valor em JavaScript possui um tipo. Que tipo é uma função?
Em JavaScript, funções são objetos.
Uma boa maneira de imaginar funções é como “objetos de ação” que podem ser chamados. Podemos não apenas chamá-los, mas também tratá-los como objetos: adicionar/remover propriedades, passar por referência etc.
Os objetos de função contêm algumas propriedades utilizáveis.
Por exemplo, o nome de uma função é acessível como a propriedade “nome”:
function digaOi() { alerta("Olá"); } alerta(digaOi.nome); //digaOi
O que é engraçado é que a lógica de atribuição de nomes é inteligente. Ele também atribui o nome correto a uma função, mesmo que ela seja criada sem ele, e então é imediatamente atribuído:
deixe dizerOi = function() { alerta("Olá"); }; alerta(digaOi.nome); // diga Olá (há um nome!)
Também funciona se a atribuição for feita através de um valor padrão:
function f(digaOi = function() {}) { alerta(digaOi.nome); // diga Olá (funciona!) } f();
Na especificação, esse recurso é chamado de “nome contextual”. Se a função não fornecer uma, então em uma atribuição ela será descoberta a partir do contexto.
Os métodos de objeto também têm nomes:
deixe usuário = { digaOi() { // ... }, digaTchau: function() { // ... } } alerta(usuário.sayHi.nome); //digaOi alerta(usuário.sayBye.nome); //dizerTchau
Mas não há mágica. Há casos em que não há como descobrir o nome certo. Nesse caso, a propriedade name está vazia, como aqui:
//função criada dentro do array deixe arr = [função() {}]; alerta(arr[0].nome); // <string vazia> // o mecanismo não tem como configurar o nome correto, então não há nenhum
Na prática, porém, a maioria das funções tem um nome.
Existe outra propriedade integrada “comprimento” que retorna o número de parâmetros de função, por exemplo:
função f1(a) {} função f2(a, b) {} função muitos(a, b, ...mais) {} alerta(f1.comprimento); //1 alerta(f2.comprimento); //2 alerta(muitos.comprimento); //2
Aqui podemos ver que os parâmetros restantes não são contados.
A propriedade length
às vezes é usada para introspecção em funções que operam em outras funções.
Por exemplo, no código abaixo, a função ask
aceita uma question
a ser feita e um número arbitrário de funções handler
para chamar.
Depois que o usuário fornece sua resposta, a função chama os manipuladores. Podemos passar dois tipos de manipuladores:
Uma função com argumento zero, que só é chamada quando o usuário dá uma resposta positiva.
Uma função com argumentos, que é chamada em ambos os casos e retorna uma resposta.
Para chamar handler
da maneira correta, examinamos a propriedade handler.length
.
A ideia é que tenhamos uma sintaxe de manipulador simples e sem argumentos para casos positivos (variante mais frequente), mas também sejamos capazes de suportar manipuladores universais:
function perguntar(pergunta, ... manipuladores) { deixe isSim = confirme(pergunta); for(deixe manipulador de manipuladores) { if (manipulador.comprimento == 0) { if (isSim) manipulador(); } outro { manipulador(éSim); } } } // para resposta positiva, ambos os manipuladores são chamados // para resposta negativa, apenas a segunda ask("Pergunta?", () => alert('Você disse sim'), resultado => alert(resultado));
Este é um caso particular do chamado polimorfismo – tratando os argumentos de forma diferente dependendo do seu tipo ou, no nosso caso, dependendo do length
. A ideia tem uso em bibliotecas JavaScript.
Também podemos adicionar propriedades próprias.
Aqui adicionamos a propriedade counter
para rastrear a contagem total de chamadas:
function digaOi() { alerta("Olá"); // vamos contar quantas vezes corremos digaOi.contador++; } digaOi.contador = 0; //valor inicial digaOi(); // Oi digaOi(); // Oi alert( `Chamado ${sayHi.counter} vezes` ); //chamou 2 vezes
Uma propriedade não é uma variável
Uma propriedade atribuída a uma função como sayHi.counter = 0
não define uma variável local counter
dentro dela. Em outras palavras, um counter
de propriedades e um let counter
de variáveis são duas coisas não relacionadas.
Podemos tratar uma função como um objeto, armazenar propriedades nela, mas isso não afeta sua execução. Variáveis não são propriedades de função e vice-versa. Estes são apenas mundos paralelos.
As propriedades da função podem substituir os encerramentos às vezes. Por exemplo, podemos reescrever o exemplo da função contador do capítulo Escopo da variável, fechamento para usar uma propriedade de função:
function makeContador() { // em vez de: // deixa contar = 0 função contador() { retornar contador.count++; }; contador.contagem = 0; contador de retorno; } deixe contador = makeCounter(); alerta(contador()); //0 alerta(contador()); //1
A count
agora é armazenada diretamente na função, não em seu ambiente lexical externo.
É melhor ou pior do que usar um fecho?
A principal diferença é que se o valor de count
residir em uma variável externa, o código externo não poderá acessá-lo. Somente funções aninhadas podem modificá-lo. E se estiver vinculado a uma função, então isso é possível:
function makeContador() { função contador() { retornar contador.count++; }; contador.contagem = 0; contador de retorno; } deixe contador = makeCounter(); contador.contagem = 10; alerta(contador()); //10
Portanto, a escolha da implementação depende dos nossos objetivos.
Expressão de Função Nomeada, ou NFE, é um termo para Expressões de Função que possuem um nome.
Por exemplo, vamos pegar uma expressão de função comum:
deixe dizerOi = function(quem) { alerta(`Olá, ${quem}`); };
E adicione um nome a ele:
deixe dizerOi = function func(quem) { alerta(`Olá, ${quem}`); };
Conseguimos alguma coisa aqui? Qual é o propósito desse nome "func"
adicional?
Primeiro, observemos que ainda temos uma expressão de função. Adicionar o nome "func"
após function
não a tornou uma Declaração de Função, porque ela ainda é criada como parte de uma expressão de atribuição.
Adicionar esse nome também não quebrou nada.
A função ainda está disponível como sayHi()
:
deixe dizerOi = function func(quem) { alerta(`Olá, ${quem}`); }; digaOi("João"); // Olá, João
Há duas coisas especiais sobre o nome func
, que são as razões para isso:
Ele permite que a função faça referência a si mesma internamente.
Não é visível fora da função.
Por exemplo, a função sayHi
abaixo chama a si mesma novamente com "Guest"
se nenhum who
for fornecido:
deixe dizerOi = function func(quem) { se (quem) { alerta(`Olá, ${quem}`); } outro { func("Convidado"); // use func para se chamar novamente } }; digaOi(); // Olá, convidado // Mas isso não funcionará: função(); // Erro, func não está definida (não visível fora da função)
Por que usamos func
? Talvez apenas use sayHi
para a chamada aninhada?
Na verdade, na maioria dos casos podemos:
deixe dizerOi = function(quem) { se (quem) { alerta(`Olá, ${quem}`); } outro { digaOi("Convidado"); } };
O problema com esse código é que sayHi
pode mudar no código externo. Se a função for atribuída a outra variável, o código começará a apresentar erros:
deixe dizerOi = function(quem) { se (quem) { alerta(`Olá, ${quem}`); } outro { digaOi("Convidado"); // Erro: sayHi não é uma função } }; seja bem-vindo = digaOi; digaOi = null; Bem-vindo(); // Erro, a chamada sayHi aninhada não funciona mais!
Isso acontece porque a função retira sayHi
de seu ambiente lexical externo. Não há sayHi
local, então a variável externa é usada. E no momento da chamada, o sayHi
externo é null
.
O nome opcional que podemos colocar na Expressão da Função destina-se a resolver exatamente esses tipos de problemas.
Vamos usá-lo para corrigir nosso código:
deixe dizerOi = function func(quem) { se (quem) { alerta(`Olá, ${quem}`); } outro { func("Convidado"); //Agora tudo bem } }; seja bem-vindo = digaOi; digaOi = null; Bem-vindo(); // Olá, convidado (a chamada aninhada funciona)
Agora funciona, porque o nome "func"
é função local. Não é tirado de fora (e não é visível lá). A especificação garante que sempre fará referência à função atual.
O código externo ainda tem sua variável sayHi
ou welcome
. E func
é um “nome de função interno”, a forma como a função pode chamar a si mesma de forma confiável.
Não existe tal coisa para declaração de função
O recurso “nome interno” descrito aqui está disponível apenas para Expressões de Função, não para Declarações de Função. Para declarações de função, não há sintaxe para adicionar um nome “interno”.
Às vezes, quando precisamos de um nome interno confiável, esse é o motivo para reescrever uma declaração de função no formato de expressão de função nomeada.
Funções são objetos.
Aqui cobrimos suas propriedades:
name
– o nome da função. Geralmente retirado da definição da função, mas se não houver nenhuma, o JavaScript tenta adivinhá-la a partir do contexto (por exemplo, uma atribuição).
length
– o número de argumentos na definição da função. Os parâmetros restantes não são contados.
Se a função for declarada como uma expressão de função (não no fluxo de código principal) e carregar o nome, ela será chamada de expressão de função nomeada. O nome pode ser usado internamente para fazer referência a si mesmo, para chamadas recursivas ou algo semelhante.
Além disso, as funções podem conter propriedades adicionais. Muitas bibliotecas JavaScript conhecidas fazem ótimo uso desse recurso.
Eles criam uma função “principal” e anexam muitas outras funções “auxiliares” a ela. Por exemplo, a biblioteca jQuery cria uma função chamada $
. A biblioteca lodash cria uma função _
e, em seguida, adiciona _.clone
, _.keyBy
e outras propriedades a ela (consulte a documentação quando quiser saber mais sobre elas). Na verdade, eles fazem isso para diminuir a poluição do espaço global, de modo que uma única biblioteca forneça apenas uma variável global. Isso reduz a possibilidade de conflitos de nomenclatura.
Portanto, uma função pode fazer um trabalho útil por si só e também carregar várias outras funcionalidades em propriedades.
importância: 5
Modifique o código de makeCounter()
para que o contador também possa diminuir e definir o número:
counter()
deve retornar o próximo número (como antes).
counter.set(value)
deve definir o contador como value
.
counter.decrease()
deve diminuir o contador em 1.
Consulte o código do sandbox para obter o exemplo completo de uso.
PS Você pode usar um encerramento ou a propriedade da função para manter a contagem atual. Ou escreva ambas as variantes.
Abra uma sandbox com testes.
A solução usa count
na variável local, mas os métodos de adição são escritos diretamente no counter
. Eles compartilham o mesmo ambiente lexical externo e também podem acessar a count
atual.
function makeContador() { deixe contar = 0; função contador() { contagem de retorno++; } contador.set = valor => contagem = valor; contador.decrease = () => contagem--; contador de retorno; }
Abra a solução com testes em uma sandbox.
importância: 2
Escreva a função sum
que funcionaria assim:
soma(1)(2) == 3; //1 + 2 soma(1)(2)(3) == 6; //1 + 2 + 3 soma(5)(-1)(2) == 6 soma(6)(-1)(-2)(-3) == 0 soma(0)(1)(2)(3)(4)(5) == 15
PS Dica: pode ser necessário configurar o objeto personalizado para conversão primitiva para sua função.
Abra uma sandbox com testes.
Para que tudo funcione de qualquer maneira , o resultado da sum
deve ser uma função.
Essa função deve manter na memória o valor atual entre as chamadas.
De acordo com a tarefa, a função deve se tornar o número quando usada em ==
. Funções são objetos, então a conversão acontece conforme descrito no capítulo Conversão de objeto para primitivo, e podemos fornecer nosso próprio método que retorna o número.
Agora o código:
função soma(a) { deixe soma atual = a; função f(b) { soma atual += b; retornar f; } f.toString = função() { retornar soma atual; }; retornar f; } alerta(soma(1)(2) ); //3 alerta(soma(5)(-1)(2) ); //6 alerta(soma(6)(-1)(-2)(-3) ); //0 alerta(soma(0)(1)(2)(3)(4)(5) ); // 15
Observe que a função sum
funciona apenas uma vez. Ele retorna a função f
.
Então, em cada chamada subsequente, f
adiciona seu parâmetro à soma currentSum
e retorna a si mesmo.
Não há recursão na última linha de f
.
Aqui está a aparência da recursão:
função f(b) { SomaAtual += b; retornarf(); // <-- chamada recursiva }
E no nosso caso, apenas retornamos a função, sem chamá-la:
função f(b) { soma atual += b; retornar f; // <-- não chama a si mesmo, retorna a si mesmo }
Este f
será usado na próxima chamada, retornando novamente, quantas vezes forem necessárias. Então, quando usado como um número ou string – o toString
retorna o currentSum
. Também poderíamos usar Symbol.toPrimitive
ou valueOf
aqui para a conversão.
Abra a solução com testes em uma sandbox.