Skip to content
This page has been auto-translated and may contain errors.View in English

Funções

docs.scrimba.com

Digamos que você está precificando itens em um carrinho. Você pega um número, adiciona imposto, arredonda e formata com um símbolo de moeda. Depois você precisa fazer novamente para o próximo item, e o próximo. Copiar esses quatro passos em todos os lugares é como bugs entram: corrija o arredondamento em um lugar e você ainda tem três outras cópias erradas. Uma função deixa você nomear esse bloco de passos uma vez e executá-lo sempre que precisar. Este capítulo é sobre escrever esses blocos, alimentá-los com entradas e obter valores de volta.

Declarando e chamando uma função

Uma função é um bloco de código nomeado que você pode executar sempre que quiser. Você escreve os passos uma vez, dá a eles um nome, e depois os executa por esse nome quantas vezes quiser.

Você cria uma com a palavra-chave function:

js
function greet() {
  console.log("Oi");
}

Essa é a definição completa, mas ainda nada aconteceu. Escrever uma função armazena os passos; isso não os executa. Para realmente executá-la, você a chama escrevendo seu nome seguido por parênteses:

js
greet(); // Oi
greet(); // Oi

Duas linhas, duas saudações, de um bloco de código. Definir e chamar são coisas separadas, e misturá-las é o primeiro passo que confunde as pessoas.

Uma função envolve um bloco de código sob um nome para que você possa executá-lo sob demanda. Você a define uma vez com a palavra-chave function, e depois a chama quantas vezes precisar:

js
function logDivider() {
  console.log("----------");
}

logDivider(); // ----------
logDivider(); // ----------

A definição configura os passos mas não executa nada. Uma função só é executada quando você a chama com (). O nome por si só, logDivider, é o valor da função; os parênteses são o que a invoca. Essa distinção é importante no momento em que você começa a passar funções por aí, porque logDivider e logDivider() são duas coisas diferentes: uma é a função, a outra é o resultado de executá-la.

Uma função é um bloco de código reutilizável vinculado a um nome. A palavra-chave function aqui cria uma declaração de função: uma função nomeada que sobe para o topo de seu escopo, significando que você pode chamá-la em uma linha acima de onde a escreveu e ainda funciona.

js
countdown(); // funciona: declarações são içadas

function countdown() {
  console.log("Pronto");
}

Esse içamento é específico da forma de declaração; as outras maneiras de definir uma função mais adiante neste capítulo não o compartilham. O nome e a chamada são valores distintos: countdown avalia para a função em si (você pode armazená-la, passá-la, compará-la), enquanto countdown() avalia para tudo o que executar a função produz. Confundir os dois está por trás de uma grande parte da confusão "por que isso é undefined", e mantê-los separados é um hábito vale a pena construir agora, antes que as funções comecem a ser passadas como argumentos para outras funções.

Dê a uma função um nome claro, tipo verbo, para o que ela faz (greet, logDivider), o mesmo cuidado que você tem ao nomear variáveis. Uma função que você chama em dez lugares é tão legível quanto seu nome.

JunoDeclarando e chamando uma função Escrever uma função armazena os passos; isso não a executa. Para executá-la, você a chama pelo nome com parênteses, como greet(). Essa divisão me confundiu no início: a definição é a receita, a chamada é realmente cozinhar. Você pode chamar a mesma função quantas vezes quiser.
JunoDeclarando e chamando uma função Definir uma função e chamá-la são dois atos separados. O bloco function configura os passos e não executa nada até você adicionar (). Mantenha greet e greet() retos em sua cabeça: um é o valor da função, o outro é o resultado de executá-la. Essa diferença morde a primeira vez que você passa uma função para algum lugar.
JunoDeclarando e chamando uma função A palavra-chave function faz uma declaração, que é içada, então você pode chamá-la acima de onde ela fica. Isso é único para essa forma; expressões e arrows não são içadas da mesma forma. E countdown é a função enquanto countdown() é seu resultado, uma distinção que importa menos quando você começa a passar funções como valores.

Parâmetros e valores de retorno

Uma função é mais útil quando você pode alimentá-la com algo. Os valores que você alimenta são parâmetros: nomes listados nos parênteses que representam tudo o que você passa quando a chama.

js
function greet(name) {
  console.log("Oi " + name);
}

greet("Maria"); // Oi Maria
greet("João"); // Oi João

name é o parâmetro. Quando você chama greet("Maria"), o texto "Maria" se encaixa em name para essa execução. Uma função, resultados diferentes, dependendo do que você passar para ela.

Até agora essas funções imprimem algo. Frequentemente você quer uma função para devolver um valor, para que você possa usá-lo depois. Isso é o que return faz:

js
function double(number) {
  return number * 2;
}

const result = double(5);
console.log(result); // 10

return envia um valor para fora da função. double(5) vira 10, e você pode armazená-lo, imprimi-lo ou passá-lo adiante. Imprimir mostra um valor na tela; retornar dá a você para continuar usando.

Funções recebem entradas através de parâmetros: os nomes nos parênteses. Os valores que você passa no local da chamada são os argumentos que os preenchem.

js
function priceWithTax(price, rate) {
  return price + price * rate;
}

const total = priceWithTax(40, 0.2);
console.log(total); // 48

price e rate são parâmetros; 40 e 0.2 são os argumentos para essa chamada. A instrução return devolve um valor para quem chamou a função, então priceWithTax(40, 0.2) avalia para 48 e você pode armazená-lo em total.

A diferença entre logging e retorno vale a pena fixar. Uma função que chama console.log mostra algo e avalia para undefined; uma função que usa return produz um valor que seu programa pode continuar usando. return também encerra a função imediatamente: qualquer linha depois disso não é executada. Se você deixar return de fora completamente, a função ainda retorna undefined, o que é uma surpresa comum quando você esperava um valor de volta e não obteve nada.

Parâmetros são as entradas nomeadas da função; argumentos são os valores concretos vinculados a eles no local da chamada. A instrução return produz o valor de saída da função e encerra a execução imediatamente, então funciona como uma saída antecipada:

js
function classify(score) {
  if (score < 0) {
    return "inválido";
  }
  if (score >= 60) {
    return "passou";
  }
  return "falhou";
}

console.log(classify(72)); // passou
console.log(classify(-3)); // inválido

Cada return tanto produz um valor quanto para a função, então uma vez que um branch dispara, nada abaixo dele é executado. Esse padrão de saída antecipada (validar e sair primeiro, lidar com o caso principal por último) o mantém fora de condicionais profundamente aninhados.

A distinção que importa no código real é entre uma função que retorna e uma que apenas produz um efeito colateral (escrever no console, mudar algo fora dela). console.log realiza um efeito colateral e a função avalia para undefined; return produz um valor que o chamador pode compor. Uma função sem return, ou um return nu sem nada depois, produz undefined. Alcançar um valor retornado de uma função que apenas faz log é um bug frequente, e a correção é quase sempre return do valor e log no local da chamada.

JunoParâmetros e valores de retorno Parâmetros são os nomes nos parênteses que representam valores que você passa, para que uma função possa dar resultados diferentes. return devolve um valor para que seu código possa usá-lo depois. Imprimir com console.log mostra na tela; retornar devolve para você. Levou um tempo para eu sentir essa diferença.
JunoParâmetros e valores de retorno Parâmetros são os nomes na definição, argumentos são os valores que você passa na chamada. return devolve um valor para o chamador e encerra a função no mesmo instante. Uma função que apenas faz log avalia para undefined, então se você esperava um valor e não obteve nada de volta, verifique se você realmente o retornou.
JunoParâmetros e valores de retornoreturn produz um valor e sai, o que torna a validação-e-saída-antecipada um padrão limpo. Observe a divisão entre retornar um valor e fazer um efeito colateral como console.log: a função de logging avalia para undefined. Quando você alcança um resultado e não obtém nada, quase sempre você fez log onde deveria ter retornado.

Funções arrow

Há uma forma mais curta de escrever uma função, chamada função arrow. Você a armazena em uma variável e usa a seta => em vez da palavra-chave function:

js
const add = (a, b) => {
  return a + b;
};

console.log(add(2, 3)); // 5

Quando a função é uma única expressão como essa, você pode descartar as chaves e o return e deixar a seta devolver o valor para você:

js
const add = (a, b) => a + b;

console.log(add(2, 3)); // 5

Ambas fazem a mesma coisa. Você verá funções arrow em todos os lugares no JavaScript moderno, então ajuda reconhecer a forma: um nome, um =, as entradas, uma seta e o corpo.

Uma função arrow é uma sintaxe mais curta para escrever uma função, armazenada em uma variável com =>:

js
const formatPrice = (amount) => `$${amount.toFixed(2)}`;

console.log(formatPrice(9.5)); // $9.50

Quando o corpo é uma única expressão, funções arrow têm um retorno implícito: sem chaves, sem palavra-chave return, o valor da expressão é devolvido automaticamente. Adicione chaves e se comporta como um corpo de função normal, onde você escreve return você mesmo:

js
const formatPrice = (amount) => {
  const rounded = amount.toFixed(2);
  return `$${rounded}`;
};

Arrows são a escolha idiomática para funções curtas e de propósito único, especialmente as pequenas que você passa para outras funções (você as encontrará com métodos de array). Para uma rotina nomeada de alto nível e reutilizável, uma declaração function comum lê claramente e funciona bem. Recorra a arrows quando a brevidade ajuda e a função é pequena.

Uma função arrow é uma expressão de função compacta escrita com =>. Com um corpo de expressão única ela tem um retorno implícito (o valor da expressão é retornado sem palavra-chave return); com um corpo entre chaves você retorna explicitamente, exatamente como uma função comum.

js
const applyDiscount = (price, percent) => price * (1 - percent / 100);

console.log(applyDiscount(80, 25)); // 60

Três maneiras de definir uma função coexistem, e as diferenças são práticas, não cosméticas:

  • Uma declaração de função (function total() {}) é içada, então é chamável acima de sua definição.
  • Uma expressão de função (const total = function () {}) não é içada da mesma forma: a variável existe mas contém undefined até a linha ser executada, então chamá-la antes lança um erro.
  • Uma função arrow (const total = () => {}) se comporta como uma expressão para içamento e adiciona uma regra-chave abaixo.

A regra que diferencia arrows: elas não vinculam seu próprio this. Uma função comum obtém um this determinado por como ela é chamada; uma arrow herda this do código circundante. Essa distinção não morde até que você esteja escrevendo métodos em objetos, então guarde o fato e o raciocínio chega lá.

Duas mais ferramentas que valem a pena usar. Parâmetros padrão dão um fallback quando um argumento está faltando, para que a função permaneça previsível:

js
const greet = (name = "ali") => `Oi ${name}`;

console.log(greet("Maria")); // Oi Maria
console.log(greet()); // Oi ali

E o hábito que mais compensa: prefira funções puras, aquelas cuja saída depende apenas de suas entradas e que não mudam nada fora delas (sem efeitos colaterais). applyDiscount acima é pura: mesmas entradas, mesmo resultado, sempre. Funções puras são aquelas que você pode testar isoladamente, raciocinar sobre sem rastrear o resto do programa e reutilizar sem surpresas. Nem tudo pode ser puro (algo tem que fazer log, buscar e mudar estado), mas colocar sua lógica em funções puras e manter os efeitos colaterais nas bordas é um dos hábitos com maior alavancagem na linguagem.

JunoFunções arrow Uma função arrow é uma forma mais curta de escrever uma função, armazenada em uma variável com => em vez da palavra-chave function. Quando o corpo é uma expressão, você pode descartar as chaves e return e a seta devolve o valor para você. Você verá essa forma constantemente, então ficar confortável em lê-la compensa rápido.
JunoFunções arrow Arrows são a forma curta para funções, e um corpo de expressão única fornece um retorno implícito: sem chaves, sem return. Adicione chaves e você escreve return você mesmo como normal. Recorra a arrows em funções pequenas e de propósito único; uma declaração function simples ainda lê muito bem para uma rotina nomeada de alto nível.
JunoFunções arrow Declarações são içadas, expressões e arrows não, e arrows também pulam seu próprio this, o que só importa uma vez que você escreve métodos de objeto. Apoie-se em parâmetros padrão para argumentos faltando, e coloque lógica em funções puras cuja saída depende apenas de suas entradas. Essas são as que você pode testar sozinhas e reutilizar sem rastrear o programa inteiro.

Para onde as funções vão daqui

Funções são como você para de se repetir: nomeie um bloco de passos, alimente-o com entradas, obtenha um valor de volta. Uma vez que isso clica, a maioria do código que você escreve se torna pequenas funções chamando outras funções, cada uma fazendo um trabalho claro.

De aqui, funções aparecem em todos os lugares. Você as passará para métodos de array para transformar listas de dados, usará para organizar a lógica de ramificação de condicionais, e se apoiará na disciplina de nomenclatura que começou com variáveis. A próxima vez que você se vê copiando um bloco de código, esse é o sinal para envolvê-lo em uma função.