Engenharia de IA Aplicada à Programação Web
Aula 3 de 8
Navegar por módulos e aulas
  1. 01 Fundamentos da Web
  2. 02 JavaScript moderno
  3. 03 TypeScript, Git e qualidade
  4. 04 Angular
  5. 05 Node.js, NestJS e APIs
  1. 2.1 Valores, variáveis e tipos
  2. 2.2 Condições e repetições
  3. 2.3 Funções, escopo e closures
  4. 2.4 Arrays e transformação de dados
  5. 2.5 Objetos, JSON e modelagem
  6. 2.6 Módulos e tratamento de erros
  7. 2.7 Promises, async/await e Fetch
  8. 2.8 Armazenamento, testes e projeto

Módulo 2 · Aula 2.3

Funções, escopo e closures

2 horas Teoria + laboratório JavaScript moderno
Ver fonte Markdown

Identificação

Introdução

O que é uma função?

Uma função é um bloco de código que recebe um nome e pode ser executado quando necessário. Ela serve para reunir uma responsabilidade e evitar a repetição da mesma regra em vários lugares.

function greetStudent() {
  return "Olá, estudante!";
}

const message = greetStudent();

Neste exemplo:

  • greetStudent é o nome da função;
  • () executa, ou chama, a função;
  • return entrega o resultado;
  • message recebe o texto retornado.

Uma função não é executada apenas por existir. Escrever greetStudent referencia a função; escrever greetStudent() chama a função.

Por que não copiar e colar o mesmo código?

Imagine que a regra de prioridade apareça no formulário, no cartão da tarefa e no relatório. Se copiarmos a regra três vezes, uma alteração futura pode atualizar apenas duas cópias. O sistema passará a fornecer respostas diferentes.

Podemos concentrar a regra:

function classifyPriority(isUrgent, isImportant) {
  if (isUrgent && isImportant) {
    return "critical";
  }

  return "normal";
}

Todos os pontos da aplicação podem chamar a mesma função.

Função e método são a mesma coisa?

Os dois representam comportamentos. Chamamos normalmente de método uma função associada a um objeto:

const title = "  Estudar funções  ";
const normalizedTitle = title.trim();

trim é um método da string. Já classifyPriority() é uma função definida pela aplicação.

O que é escopo?

Escopo é a região do código em que um nome pode ser acessado:

function createMessage() {
  const message = "Disponível apenas dentro da função";
  return message;
}

console.log(message); // ReferenceError

O escopo impede que todos os valores fiquem disponíveis em todos os lugares. Isso reduz conflitos e protege detalhes internos.

O que é closure?

Closure é a capacidade de uma função lembrar o ambiente em que foi criada, mesmo depois que a função externa terminou:

function createCounter() {
  let count = 0;

  return function increment() {
    count += 1;
    return count;
  };
}

const countExecution = createCounter();

countExecution(); // 1
countExecution(); // 2

count não é global e não pode ser alterado diretamente de fora. A função increment continua tendo acesso a ele. Closures aparecem em contadores, configurações, eventos, fábricas de funções e estado privado.

Onde isso aparece no projeto?

Nesta aula transformaremos validação, normalização e classificação em funções reutilizáveis. O laboratório mostrará os argumentos, o retorno de cada regra e um contador de execuções mantido por closure.

Objetivos

Ao final da aula, o aluno deverá conseguir:

  1. explicar por que funções reduzem duplicação;
  2. diferenciar declarar, referenciar e chamar uma função;
  3. utilizar parâmetros, argumentos e return;
  4. criar funções com declaração, expressão e arrow function;
  5. escolher nomes e responsabilidades claras;
  6. reconhecer escopo global, de função e de bloco;
  7. explicar diferenças relevantes entre var, let e const;
  8. diferenciar função pura e efeito colateral;
  9. utilizar funções como valores e callbacks;
  10. construir e explicar uma closure.

Pré-requisitos

  • Aulas 2.1 e 2.2 concluídas;
  • variáveis, tipos, operadores e condições;
  • noções de eventos e DOM;
  • navegador, DevTools e editor de código.

Pergunta orientadora

Como transformar regras repetidas em unidades reutilizáveis sem expor todos os dados globalmente?

Roteiro sugerido

Etapa Duração
Introdução e anatomia de uma função 20 min
Parâmetros, argumentos e retorno 20 min
Formas de declarar e funções como valores 20 min
Escopo e closures 30 min
Laboratório 25 min
Revisão 5 min

1. Declarar, referenciar e chamar

Declarar:

function calculateTotal(price, quantity) {
  return price * quantity;
}

Referenciar:

const selectedRule = calculateTotal;

Chamar:

const total = calculateTotal(25, 3);

Ao referenciar, não usamos (). Ao chamar, usamos os parênteses e a função executa.

Isso é importante em eventos:

button.addEventListener("click", handleClick);

Passamos a referência handleClick. Se escrevêssemos handleClick(), executaríamos a função imediatamente e passaríamos o resultado dela.

2. Parâmetros e argumentos

Parâmetros são os nomes definidos pela função:

function calculateTotal(price, quantity) {
  return price * quantity;
}

Argumentos são os valores fornecidos na chamada:

calculateTotal(25, 3);

Aqui:

  • price e quantity são parâmetros;
  • 25 e 3 são argumentos.

Parâmetro padrão

Um valor padrão é usado quando o argumento é undefined:

function createGreeting(name = "estudante") {
  return `Olá, ${name}!`;
}

createGreeting(); // "Olá, estudante!"

O padrão não substitui null, string vazia ou zero.

3. return

return encerra a função e entrega um valor:

function double(value) {
  return value * 2;
}

Sem return explícito, o resultado é undefined:

function doubleWithoutReturn(value) {
  value * 2;
}

doubleWithoutReturn(4); // undefined

return não é console.log

function calculateTotal(price, quantity) {
  console.log(price * quantity);
}

Essa função exibe algo no console, mas não devolve o total. Prefira retornar a informação e decidir fora da regra onde apresentá-la:

const total = calculateTotal(25, 3);
output.textContent = String(total);

4. Retorno antecipado

Um retorno antecipado encerra casos inválidos antes da regra principal:

function validateTitle(title) {
  const normalizedTitle = title.trim();

  if (normalizedTitle.length < 3) {
    return {
      isValid: false,
      message: "O título precisa ter pelo menos 3 caracteres.",
    };
  }

  return {
    isValid: true,
    message: "Título válido.",
  };
}

Esse padrão também é chamado de guard clause. Ele reduz níveis de aninhamento.

5. Declaração de função

function classifyEstimate(hours) {
  if (hours <= 1) {
    return "short";
  }

  if (hours <= 4) {
    return "medium";
  }

  return "long";
}

Declarações possuem nome claro e podem ser chamadas antes da linha em que aparecem, porque sua declaração é içada durante a preparação do escopo.

showMessage();

function showMessage() {
  console.log("Funcionou");
}

Esse comportamento é conhecido como hoisting. Ainda assim, organizar o código de forma previsível facilita a leitura.

6. Expressão de função

Uma função também pode ser armazenada em uma variável:

const classifyEstimate = function (hours) {
  return hours <= 1 ? "short" : "long";
};

Aqui, a função é criada como valor e associada a classifyEstimate.

Não é possível chamar a constante antes de sua inicialização:

classifyEstimate(2); // ReferenceError

const classifyEstimate = function (hours) {
  return "medium";
};

7. Arrow function

Uma arrow function oferece sintaxe compacta:

const double = (value) => {
  return value * 2;
};

Quando existe apenas uma expressão, o retorno pode ser implícito:

const double = (value) => value * 2;

Com um único parâmetro, os parênteses são opcionais:

const normalize = value => value.trim();

Neste curso, manteremos os parênteses por consistência:

const normalize = (value) => value.trim();

Arrow function não substitui tudo

Arrow functions não criam seu próprio this e não possuem arguments. Isso será relevante ao trabalhar com objetos e classes. Para regras pequenas e callbacks, elas são frequentemente convenientes. Para funções principais, escolha a forma que comunique melhor a intenção.

8. Uma responsabilidade por função

Compare:

function processTask(task) {
  // valida, normaliza, classifica, salva, atualiza DOM e registra log
}

Com regras separadas:

function normalizeTitle(title) {
  return title.trim();
}

function validateTitle(title) {
  return title.length >= 3;
}

function classifyPriority(isUrgent, isImportant) {
  // regra de classificação
}

Funções menores:

  • recebem menos informações;
  • são mais fáceis de nomear;
  • podem ser reutilizadas;
  • produzem testes mais simples;
  • isolam mudanças.

Não existe uma quantidade mágica de linhas. Separe quando houver responsabilidades ou motivos de mudança diferentes.

9. Funções puras

Uma função pura:

  1. produz o mesmo resultado para os mesmos argumentos;
  2. não altera estado externo observável.
function calculateSubtotal(price, quantity) {
  return price * quantity;
}

Não pura:

let total = 0;

function addToTotal(value) {
  total += value;
}

A segunda depende e altera uma variável externa.

Interfaces sempre terão efeitos colaterais, como atualizar o DOM ou enviar uma requisição. A estratégia é manter as regras centrais puras e concentrar efeitos nas bordas:

const result = classifyPriority(isUrgent, isImportant); // regra pura
output.textContent = result; // efeito no DOM

10. Funções são valores

JavaScript permite armazenar, passar e retornar funções:

function executeRule(rule, value) {
  return rule(value);
}

function double(value) {
  return value * 2;
}

executeRule(double, 5); // 10

Uma função recebida por outra função é frequentemente chamada de callback.

Eventos utilizam callbacks:

form.addEventListener("submit", (event) => {
  event.preventDefault();
});

O navegador chama o callback quando o evento ocorre.

11. Escopo global

Um nome declarado fora das funções e blocos pertence ao escopo externo do script:

const applicationName = "Painel de tarefas";

function showApplicationName() {
  return applicationName;
}

A função interna consegue acessar o escopo externo.

Evite colocar todos os valores no escopo global. Muitos nomes globais aumentam conflitos, dependências ocultas e alterações acidentais.

12. Escopo de função

Parâmetros e variáveis declaradas dentro de uma função não ficam disponíveis fora:

function calculateTotal(price, quantity) {
  const total = price * quantity;
  return total;
}

console.log(total); // ReferenceError

Cada chamada cria seu próprio ambiente:

calculateTotal(10, 2);
calculateTotal(30, 4);

Os valores de uma chamada não substituem os valores internos da outra.

13. Escopo de bloco

let e const respeitam blocos:

if (true) {
  const message = "Somente dentro do bloco";
  let count = 1;
}

console.log(message); // ReferenceError

Blocos aparecem em if, for, while e chaves criadas deliberadamente.

14. var, let e const

var possui escopo de função, não de bloco:

if (true) {
  var legacyMessage = "Ainda acessível fora do bloco";
}

console.log(legacyMessage);

Além disso, sua declaração sofre hoisting com inicialização em undefined, o que pode esconder erros.

Prática moderna:

  • utilize const por padrão;
  • utilize let quando a associação precisar mudar;
  • evite var em código novo.

15. Sombreamento de nomes

Um escopo interno pode declarar um nome igual ao externo:

const status = "global";

function inspectStatus() {
  const status = "local";
  return status;
}

O status local sombreia o externo dentro da função. Isso é permitido, mas nomes iguais em escopos próximos podem dificultar a leitura.

16. Escopo léxico

JavaScript decide quais nomes uma função pode acessar com base no local onde ela foi definida, não no local onde foi chamada:

const label = "externa";

function createReader() {
  const label = "interna";

  return function readLabel() {
    return label;
  };
}

const readLabel = createReader();
readLabel(); // "interna"

Esse comportamento é a base das closures.

17. Closure passo a passo

function createExecutionCounter() {
  let count = 0;

  return function countExecution() {
    count += 1;
    return count;
  };
}

Passos:

  1. createExecutionCounter() cria count;
  2. a função interna utiliza count;
  3. a função interna é retornada;
  4. mesmo após a função externa terminar, a função retornada lembra count;
  5. cada chamada atualiza o mesmo valor privado.
const counterA = createExecutionCounter();
const counterB = createExecutionCounter();

counterA(); // 1
counterA(); // 2
counterB(); // 1

Cada execução da função externa cria um novo ambiente.

Closure não é apenas função aninhada

Uma função aninhada demonstra closure quando mantém acesso ao ambiente léxico externo. O ponto importante é a preservação desse acesso, não apenas a presença de duas funções.

18. Fábrica de funções

Closures permitem configurar uma regra:

function createMinimumLengthValidator(minimumLength) {
  return function validate(value) {
    return value.trim().length >= minimumLength;
  };
}

const validateTaskTitle = createMinimumLengthValidator(3);
const validatePassword = createMinimumLengthValidator(12);

Cada validador lembra sua própria configuração.

19. Composição de regras

Uma função pode coordenar funções menores:

function evaluateTask(rawTask) {
  const title = normalizeTitle(rawTask.title);
  const titleValidation = validateTitle(title);
  const priority = classifyPriority(
    rawTask.isUrgent,
    rawTask.isImportant,
  );
  const size = classifyEstimate(rawTask.estimate);

  return {
    title,
    titleValidation,
    priority,
    size,
  };
}

evaluateTask não precisa repetir a implementação de cada regra. Ela organiza as chamadas e reúne os resultados.

20. Laboratório guiado

Abra a biblioteca interativa de regras.

Etapa 1 — Execute as regras

  1. informe o título de uma tarefa;
  2. escolha urgência e importância;
  3. defina uma estimativa;
  4. clique em Executar biblioteca;
  5. observe argumentos e retornos.

Etapa 2 — Observe as funções puras

Execute duas vezes com os mesmos dados. As funções de normalização, validação e classificação devem retornar os mesmos resultados.

Etapa 3 — Observe a closure

O contador cresce a cada execução, mesmo sem ser global. Recarregue a página: um novo contador será criado e começará novamente em zero.

Etapa 4 — Inspecione o código

Localize em assets/main.js:

  • normalizeTitle;
  • createMinimumLengthValidator;
  • classifyPriority;
  • classifyEstimate;
  • evaluateTask;
  • createExecutionCounter;
  • o callback do evento submit.

21. Erros comuns

Esquecer os parênteses ao chamar

const result = classifyPriority;

Isso guarda a função. Para obter a classificação:

const result = classifyPriority(true, false);

Esquecer return

const double = (value) => {
  value * 2;
};

Com chaves, o retorno precisa ser explícito:

const double = (value) => {
  return value * 2;
};

Alterar argumentos sem necessidade

Objetos e arrays podem ser alterados dentro de uma função. Regras puras devem preferir criar um resultado em vez de modificar a entrada.

Criar funções com parâmetros demais

Muitos parâmetros podem indicar responsabilidades excessivas ou a necessidade de agrupar dados relacionados em um objeto. Estudaremos modelagem de objetos na Aula 2.5.

Depender de globais

let currentPriority = "low";

function classify() {
  currentPriority = "critical";
}

Prefira retornar:

function classify() {
  return "critical";
}

Usar closure sem necessidade

Closure é uma ferramenta, não uma obrigação. Se um valor pode ser passado claramente como argumento, essa pode ser a solução mais simples.

22. Boas práticas

  • dê nomes verbais às funções;
  • mantenha uma responsabilidade principal;
  • prefira poucos parâmetros explícitos;
  • retorne valores em vez de apenas imprimir;
  • use retornos antecipados para casos inválidos;
  • mantenha regras centrais puras quando possível;
  • limite efeitos colaterais às bordas;
  • use const por padrão;
  • documente o motivo, não traduza cada linha em comentário;
  • teste entradas normais, limites e casos inválidos.

23. Exercícios

Exercício 1 — Saudação

Crie createGreeting(name = "estudante") e retorne uma mensagem.

Exercício 2 — Regra pura

Crie calculateProgress(completed, total) e retorne a porcentagem. Trate total igual a zero.

Exercício 3 — Callback

Crie executeRule(rule, value) e passe uma função que normalize texto.

Exercício 4 — Escopo

Declare valores com const, let e var dentro de um bloco e registre quais ficam disponíveis fora. Explique o resultado.

Exercício 5 — Closure

Crie createLimitedCounter(limit) que aumente até o limite e depois preserve o valor máximo.

24. Desafio

Amplie o laboratório com:

  • uma regra para detectar tarefa longa;
  • um validador configurável de estimativa máxima;
  • um segundo contador independente;
  • um botão que reinicie apenas o estado visual, sem recarregar a página;
  • testes manuais documentados para cada função pura.

25. Lista de verificação de conclusão

  • Sei diferenciar declarar, referenciar e chamar.
  • Sei explicar parâmetro e argumento.
  • Entendo que console.log não substitui return.
  • Consigo criar declaração e arrow function.
  • Sei identificar uma função pura.
  • Entendo escopo global, de função e de bloco.
  • Sei por que evitamos var.
  • Consigo explicar uma closure sem apenas repetir a definição.
  • Testei o contador independente.
  • Inspecionei o laboratório no DevTools.

26. Critérios de avaliação

Critério Pontos
Funções pequenas e bem nomeadas 25
Parâmetros e retornos corretos 20
Regras puras e composição 20
Uso e explicação de escopo 15
Closure funcionando e explicada 15
Acessibilidade e organização 5
Total 100

Resumo

Nesta aula, aprendemos que:

  • funções encapsulam responsabilidades;
  • parâmetros recebem argumentos e return entrega resultados;
  • funções podem ser armazenadas, passadas e retornadas;
  • escopo controla onde nomes estão disponíveis;
  • const e let respeitam blocos;
  • funções puras tornam regras mais previsíveis;
  • closures preservam acesso ao ambiente onde a função foi criada.

Próxima aula

Na Aula 2.4, utilizaremos arrays e seus métodos para mapear, filtrar, reduzir, ordenar e calcular métricas sem alterar os dados originais indevidamente.