Uma função é um bloco paramétrico de código definido uma vez e chamado várias vezes mais tarde. Em JavaScript uma função é composta e influenciada por muitos componentes:
- código JavaScript que forma o corpo da função
- A lista de parâmetros
- As variáveis acessíveis a partir do âmbito léxico
- O valor retornado
- O contexto
this
quando a função é invocada - Nome ou uma função anónima
- A variável que detém o objecto da função
-
arguments
objecto (ou em falta numa função de seta)
p> Este post ensina-lhe seis abordagens para declarar funções JavaScript: a sintaxe, exemplos e armadilhas comuns. Além disso, compreenderá quando utilizar um tipo específico de função em determinadas circunstâncias.
1. Declaração de função
Uma declaração de função é feita de
function
palavra-chave, seguida de um nome de função obrigatório, uma lista de parâmetros num par de parênteses(para1, ..., paramN)
e um par de chaves de caracóis{...}
que delimita o código do corpo.
Um exemplo de declaração de função:
// function declarationfunction isEven(num) { return num % 2 === 0;}isEven(24); // => trueisEven(11); // => false
function isEven(num) {...}
é uma declaração de função que define isEven
função, que determina se um número é par.
A declaração de função cria uma variável no âmbito actual com o identificador igual ao nome da função. Esta variável detém o objecto da função.
A variável função é içada até ao topo do âmbito actual, o que significa que a função pode ser invocada antes da declaração (ver este capítulo para mais detalhes).
A função criada é nomeada, o que significa que o name
propriedade do objecto da função detém o seu nome. É útil ao visualizar a pilha de chamadas: na depuração ou na leitura de mensagens de erro.
Vejamos estas propriedades num exemplo:
// Hoisted variableconsole.log(hello('Aliens')); // => 'Hello Aliens!'// Named functionconsole.log(hello.name) // => 'hello'// Variable holds the function objectconsole.log(typeof hello); // => 'function'function hello(name) { return `Hello ${name}!`;}
A declaração da função function hello(name) {...}
cria uma variável hello
que é içada para o topo do âmbito actual. hello
variável detém o objecto da função e hello.name
contém o nome da função: 'hello'
.
1.1 Uma função regular
A declaração da função corresponde aos casos em que é necessária uma função regular. Regular significa que declara a função uma vez e mais tarde invoca-a em muitos lugares diferentes. Este é o cenário básico:
function sum(a, b) { return a + b;}sum(5, 6); // => 11().reduce(sum) // => 10
Porque a declaração de função cria uma variável no âmbito actual, juntamente com as chamadas de função regulares, é útil para a repetição ou destacamento de ouvintes de eventos. Ao contrário das expressões de função ou funções de seta, que não criam uma ligação com a variável de função pelo seu nome.
Por exemplo, para calcular recursivamente o factorial, tem de aceder à função dentro:
function factorial(n) { if (n === 0) { return 1; } return n * factorial(n - 1);}factorial(4); // => 24
Inside factorial()
está a ser feita uma chamada recursiva utilizando a variável que detém a função: factorial(n - 1)
.
É possível usar uma expressão de função e atribuí-la a uma variável regular, por exemplo const factorial = function(n) {...}
. Mas a declaração de função function factorial(n)
é compacta (não há necessidade de const
e =
).
Uma propriedade importante da declaração de função é o seu mecanismo de elevação. Ela permite utilizar a função antes da declaração no mesmo âmbito.
Hoisting é útil em algumas situações. Por exemplo, quando se gostaria de ver como a função é chamada no início de um script, sem ler a implementação da função. A implementação da função pode ser localizada abaixo no ficheiro, pelo que nem sequer se pode deslocar até lá.
Pode ler mais detalhes sobre a declaração da função içada aqui.
1.2 Diferença da expressão da função
É fácil confundir a declaração da função com a expressão da função. Têm um aspecto muito semelhante mas produzem funções com propriedades diferentes.
Uma regra fácil de lembrar: a declaração da função numa declaração começa sempre com a palavra-chave function
. Caso contrário, é uma expressão de função (ver 2.).
A amostra seguinte é uma declaração de função em que a declaração começa com function
palavra-chave:
// Function declaration: starts with "function"function isNil(value) { return value == null;}
No caso de expressões de função, a declaração JavaScript não começa com function
palavra-chave (está presente algures no meio do código da declaração):
// Function expression: starts with "const"const isTruthy = function(value) { return !!value;};// Function expression: an argument for .filter()const numbers = ().filter(function(item) { return typeof item === 'number';});// Function expression (IIFE): starts with "("(function messageFunction(message) { return message + ' World!';})('Hello');
1.3 Declaração de função em condições
alguns ambientes JavaScript podem lançar um erro de referência ao invocar uma função cuja declaração aparece dentro de blocos {...}
de if
for
ou while
declarações.
Vamos activar o modo restrito e ver o que acontece quando uma função é declarada num condicional:
(function() { 'use strict'; if (true) { function ok() { return 'true ok'; } } else { function ok() { return 'false ok'; } } console.log(typeof ok === 'undefined'); // => true console.log(ok()); // Throws "ReferenceError: ok is not defined"})();
Ao chamar ok()
, o JavaScript lança ReferenceError: ok is not defined
, porque a declaração da função está dentro de um bloco condicional.
A declaração de função em condições é permitida em modo não condicional, o que a torna ainda mais confusa.
Como regra geral para estas situações, quando uma função deve ser criada por condições – usar uma expressão de função. Vejamos como é possível:
(function() { 'use strict'; let ok; if (true) { ok = function() { return 'true ok'; }; } else { ok = function() { return 'false ok'; }; } console.log(typeof ok === 'function'); // => true console.log(ok()); // => 'true ok'})();
Porque a função é um objecto regular, atribui-la a uma variável dependendo da condição. A invocação ok()
funciona bem, sem erros.
2. Expressão da função
Uma expressão da função é determinada por uma
function
palavra-chave, seguida por um nome opcional da função, uma lista de parâmetros num par de parênteses(para1, ..., paramN)
e um par de chaves de caracóis{ ... }
que delimita o código do corpo.
algumas amostras da expressão da função:
const count = function(array) { // Function expression return array.length;}const methods = { numbers: , sum: function() { // Function expression return this.numbers.reduce(function(acc, num) { // func. expression return acc + num; }); }}count(); // => 3methods.sum(); // => 14
A expressão da função cria um objecto de função que pode ser utilizado em diferentes situações:
- Atribuído a uma variável como objecto
count = function(...) {...}
- Cria um método sobre um objecto
sum: function() {...}
- Utilizar a função como um cavalo de trabalho em JavaScript. Normalmente, lida com este tipo de declaração de função, juntamente com a função de seta (se preferir uma sintaxe curta e um contexto lexical).
2.1 Expressão de função nomeada
Uma função é anónima quando não tem nome (
name
propriedade é uma cadeia vazia''
):( function(variable) {return typeof variable; }).name; // => ''
Esta é uma função anónima, cujo nome é uma cadeia vazia.
Por vezes, o nome da função pode ser inferido. Por exemplo, quando o anónimo é atribuído a uma variável:
const myFunctionVar = function(variable) { return typeof variable; };myFunctionVar.name; // => 'myFunctionVar'
O nome da função anónima é
'myFunctionVar'
, porquemyFunctionVar
o nome da variável é utilizado para inferir o nome da função.Quando a expressão tem o nome especificado, esta é uma expressão de função nomeada. Tem algumas propriedades adicionais em comparação com uma simples expressão de função:
- É criada uma função nomeada, ou seja
name
propriedade detém o nome da função - Dentro do corpo da função uma variável com o mesmo nome detém o objecto da função
Vamos utilizar o exemplo acima, mas definir um nome na expressão da função:
const getType = function funName(variable) { console.log(typeof funName === 'function'); // => true return typeof variable;}console.log(getType(3)); // => 'number'console.log(getType.name); // => 'funName'console.log(typeof funName); // => 'undefined'
function funName(variable) {...}
é uma expressão de função nomeada. A variávelfunName
é acessível dentro do âmbito da função, mas não fora. De qualquer modo, a propriedadename
do objecto da função detém o nome:funName
.2.2 Expressão da função nomeada por favor
Quando uma expressão da função
const fun = function() {}
é atribuída a uma variável, alguns motores inferem o nome da função a partir desta variável. No entanto, as chamadas de retorno podem ser passadas como expressões anónimas de função, sem armazenamento em variáveis: por isso, o motor não pode determinar o seu nome.É razoável favorecer funções nomeadas e evitar funções anónimas para obter benefícios como:
- As mensagens de erro e pilhas de chamadas mostram informações mais detalhadas quando se utilizam os nomes das funções
- Depuração mais confortável, reduzindo o número de nomes de pilhas anónimas
- O nome da função diz o que a função faz
- Pode aceder à função dentro do seu âmbito para chamadas recursivas ou destacamento de ouvintes de eventos
3. Shorthand method definition
Shorthand method definition pode ser usado numa declaração de método sobre literals de objectos e classes ES2015. Pode defini-los usando um nome de função, seguido por uma lista de parâmetros num par de parênteses
(para1, ..., paramN)
e um par de chaves de caracóis{ ... }
que delimita as declarações do corpo.O exemplo seguinte utiliza uma definição de método abreviado num objecto literal:
const collection = { items: , add(...items) { this.items.push(...items); }, get(index) { return this.items; }};collection.add('C', 'Java', 'PHP');collection.get(1) // => 'Java'
add()
eget()
métodos emcollection
objecto são definidos utilizando uma definição curta do método. Estes métodos são chamados como habitualmente:collection.add(...)
ecollection.get(...)
.A abordagem curta da definição do método tem vários benefícios sobre a definição de propriedade tradicional com um nome, dois pontos
:
e uma expressão de funçãoadd: function(...) {...}
:- Uma sintaxe mais curta é mais fácil de compreender
- A definição do método de corthand cria uma função nomeada, ao contrário de uma expressão de função. É útil para depuração.
O
class
a sintaxe requer declarações de método de forma curta:class Star { constructor(name) { this.name = name; } getMessage(message) { return this.name + message; }}const sun = new Star('Sun');sun.getMessage(' is shining') // => 'Sun is shining'
3.1 Nomes e métodos de propriedade calculados
ECMAScript 2015 acrescenta uma característica agradável: nomes de propriedade calculados em literals e classes de objectos.
As propriedades computadas utilizam uma sintaxe ligeiramente diferente() {...}
, pelo que a definição do método tem este aspecto:const addMethod = 'add', getMethod = 'get';const collection = { items: , (...items) { this.items.push(...items); }, (index) { return this.items; }};collection('C', 'Java', 'PHP');collection(1) // => 'Java'
(...) {...}
e(...) {...}
são declarações abreviadas do método com nomes de propriedades computadas.4. Função seta
Uma função seta é definida usando um par de parênteses que contém a lista de parâmetros
(param1, param2, ..., paramN)
, seguido por uma seta gorda=>
e um par de chaves de caracóis{...}
que delimita as declarações do corpo.Quando a função de seta tem apenas um parâmetro, o par de parênteses pode ser omitido. Quando contém uma única declaração, o par de parênteses também pode ser omitido.
p>Vejamos o uso básico da função de seta:
const absValue = (number) => { if (number < 0) { return -number; } return number;}absValue(-10); // => 10absValue(5); // => 5
absValue
é uma função de seta que calcula o valor absoluto de um número.A função declarada usando uma seta gorda tem as seguintes propriedades:
- A função de seta não cria o seu contexto de execução, mas toma-a lexicalmente (ao contrário da expressão da função ou declaração da função, que cria a própria
this
dependendo da invocação) - A função de seta é anónima. Contudo, o motor pode inferir o seu nome a partir da variável que detém a função.
-
arguments
objecto não está disponível na função de seta (ao contrário de outros tipos de declaração que fornecemarguments
objecto). É livre de utilizar parâmetros de repouso(...params)
, no entanto.
4.1 Transparência do contexto
this
palavra-chave é um aspecto confuso do JavaScript (verifique este artigo para uma explicação detalhada sobrethis
).
Porque as funções criam um contexto de execução próprio, muitas vezes é difícil detectarthis
valor.ECMAScript 2015 melhora
this
utilização introduzindo a função de seta, que toma o contexto lexicalmente (ou simplesmente usathis
a partir do âmbito exterior imediato). Isto é bom porque não tem de usar.bind(this)
ou armazenar o contextovar self = this
quando uma função precisa do contexto envolvente.vejamos como
this
é herdado da função exterior:class Numbers { constructor(array) { this.array = array; } addNumber(number) { if (number !== undefined) { this.array.push(number); } return (number) => { console.log(this === numbersObject); // => true this.array.push(number); }; }}const numbersObject = new Numbers();const addMethod = numbersObject.addNumber();addMethod(1);addMethod(5);console.log(numbersObject.array); // =>
Numbers
classe contém um conjunto de números e fornece um métodoaddNumber()
para inserir novos números.
QuandoaddNumber()
é chamado sem argumentos, é devolvido um fecho que permite inserir números. Este fecho é uma função de seta que temthis
comonumbersObject
instância porque o contexto é tomado lexicamente deaddNumbers()
método.Sem a função de seta, é necessário fixar manualmente o contexto. Significa utilizar soluções de trabalho como
.bind()
método://... return function(number) { console.log(this === numbersObject); // => true this.array.push(number); }.bind(this);//...
ou armazenar o contexto numa variável separada
var self = this
://... const self = this; return function(number) { console.log(self === numbersObject); // => true self.array.push(number); };//...
A transparência do contexto pode ser usada quando se pretende manter
this
como está, retirado do contexto envolvente.4.2 Curtas chamadas de retorno
Quando se cria uma função de seta, os pares de parênteses e os parênteses curvos são opcionais para um único parâmetro e uma única declaração de corpo. Isto ajuda na criação de funções de revogação muito curtas.
Vamos fazer uma função que descobre se uma matriz contém
0
:const numbers = ;numbers.some(item => item === 0); // => true
item => item === 0
é uma função de seta que parece directa.Nota que as funções de seta curta aninhadas são difíceis de ler. A forma mais conveniente de utilizar a forma de função de seta curta é uma única chamada de retorno (sem aninhamento).
Se necessário, utilizar a sintaxe expandida das funções de seta ao escrever funções de seta aninhada. É simplesmente mais fácil de ler.
5. Função Gerador
A função gerador em JavaScript retorna um objecto Gerador. A sua sintaxe é semelhante à expressão de função, declaração de função ou declaração de método, apenas que requer um carácter estrela
*
.A função geradora pode ser declarada nas seguintes formas:
a. Formulário de declaração da função
function* <name>()
:function* indexGenerator(){ var index = 0; while(true) { yield index++; }}const g = indexGenerator();console.log(g.next().value); // => 0console.log(g.next().value); // => 1
b. Forma de expressão da função
function* ()
:const indexGenerator = function* () { let index = 0; while(true) { yield index++; }};const g = indexGenerator();console.log(g.next().value); // => 0console.log(g.next().value); // => 1
c. Formulário de definição do método abreviado
*<name>()
:const obj = { *indexGenerator() { var index = 0; while(true) { yield index++; } }}const g = obj.indexGenerator();console.log(g.next().value); // => 0console.log(g.next().value); // => 1
Nos 3 casos a função gerador devolve o objecto gerador
g
. Mais tardeg
é usado para gerar séries de números incrementais.6. Mais uma coisa: nova Função
Em JavaScript as funções são objectos de primeira classe – uma função é um objecto regular do tipo
function
.
As formas da declaração descrita acima criam o mesmo tipo de objecto de função. Vejamos um exemplo:function sum1(a, b) { return a + b;}const sum2 = function(a, b) { return a + b;}const sum3 = (a, b) => a + b;console.log(typeof sum1 === 'function'); // => trueconsole.log(typeof sum2 === 'function'); // => trueconsole.log(typeof sum3 === 'function'); // => true
O tipo de objecto função tem um construtor:
Function
.
QuandoFunction
é invocado como construtornew Function(arg1, arg2, ..., argN, bodyString)
, é criada uma nova função. Os argumentosarg1, args2, ..., argN
passados para construtor tornam-se os nomes dos parâmetros para a nova função e o último argumentobodyString
é usado como o código do corpo da função.Vamos criar uma função que soma dois números:
const numberA = 'numberA', numberB = 'numberB';const sumFunction = new Function(numberA, numberB, 'return numberA + numberB');sumFunction(10, 15) // => 25
sumFunction
criado comFunction
construtor invocação tem parâmetrosnumberA
enumberB
e o corporeturn numberA + numberB
.As funções criadas desta forma não têm acesso ao âmbito actual, pelo que os fechos não podem ser criados. Elas são sempre criadas no âmbito global.
Uma possível aplicação de
new Function
é uma melhor forma de aceder ao objecto global num browser ou script NodeJS:(function() { 'use strict'; const global = new Function('return this')(); console.log(global === window); // => true console.log(this === window); // => false})();
Lembrem-se que as funções quase nunca devem ser declaradas utilizando
new Function()
. Porque o corpo da função é avaliado em tempo de execução, esta abordagem herda muitoseval()
problemas de utilização: riscos de segurança, depuração mais difícil, nenhuma forma de aplicar optimizações do motor, nenhum editor auto-completar.7. No final, qual a melhor forma?
não há vencedor ou perdedor. A decisão sobre o tipo de declaração a escolher depende da situação.
Existem no entanto algumas regras que pode seguir em situações comuns.
Se a função utilizar
this
da função envolvente, a função seta é uma boa solução. Quando a função de retorno tem uma declaração curta, a função de seta é também uma boa opção, porque cria código curto e leve.Para uma sintaxe mais curta ao declarar métodos sobre objectos literais, é preferível a declaração do método abreviado.
new Function
a forma de declarar funções normalmente não deve ser utilizada. Principalmente porque abre potenciais riscos de segurança, não permite o auto-completar do código nos editores e perde as optimizações do motor. - É criada uma função nomeada, ou seja