Programação II – ECO Aulas JavaScript Moderno

Funções, Callbacks e Arrow Functions em JavaScript

Nesta unidade entendemos como as funções funcionam em JavaScript — da sintaxe clássica às arrow functions — e aprendemos o conceito de callback, base de toda programação assíncrona e orientada a eventos na web.

Introdução

Em JavaScript, funções são cidadãs de primeira classe: elas podem ser atribuídas a variáveis, passadas como argumentos para outras funções e retornadas como resultado de outras funções. Esse comportamento é a base de padrões muito usados no desenvolvimento web — eventos de clique, requisições HTTP, temporizadores e muito mais.

Nesta unidade vamos estudar três formas de declarar funções, entender o conceito de escopo, aprender o que são callbacks e conhecer as funções de ordem superior mais usadas no dia a dia com JavaScript.

Para estudantes que faltaram: todos os exemplos desta página podem ser copiados para um arquivo .js e executados com node arquivo.js no terminal, ou colados diretamente no console do navegador (F12 → aba Console). Recomendamos reproduzir e modificar cada exemplo antes de avançar para o próximo.

1. Funções em JavaScript

1.1 Declaração de função

É a forma mais clássica. A palavra-chave function é seguida pelo nome, pelos parâmetros entre parênteses e pelo corpo entre chaves.

// Declaração de função
function saudar(nome) {
  return "Olá, " + nome + "!";
}

// Chamando (invocando) a função
console.log(saudar("Ana"));   // Olá, Ana!
console.log(saudar("João"));  // Olá, João!
Entendendo linha a linha:
  • function saudar(nome) — declara uma função chamada saudar que recebe um parâmetro chamado nome.
  • return — devolve um valor para quem chamou a função. Sem return, a função retorna undefined.
  • saudar("Ana") — chama a função passando "Ana" como argumento. O parâmetro nome recebe esse valor dentro da função.

1.2 Expressão de função

Uma função também pode ser atribuída a uma variável. Essa forma é chamada de expressão de função. A diferença prática é que expressões de função não são içadas (hoisting) — precisam ser definidas antes de serem usadas.

// Expressão de função
const dobrar = function(numero) {
  return numero * 2;
};

console.log(dobrar(5));    // 10
console.log(dobrar(12));   // 24
console.log(typeof dobrar); // "function"

1.3 Parâmetros com valor padrão

JavaScript permite definir valores padrão para parâmetros, usados quando o argumento não for passado pelo chamador.

function criarMensagem(texto, autor = "Anônimo") {
  return `"${texto}" — ${autor}`;
}

console.log(criarMensagem("Aprender é crescer", "Maria"));
// "Aprender é crescer" — Maria

console.log(criarMensagem("Tente sempre"));
// "Tente sempre" — Anônimo  ← usou o valor padrão
O texto entre crases (`) com ${} é chamado de template literal. Ele permite embutir expressões JavaScript diretamente dentro de strings, sem precisar concatenar com +.

1.4 Funções que retornam funções

Como funções são valores, uma função pode retornar outra função. Esse padrão será muito útil quando estudarmos closures e callbacks.

function criarMultiplicador(fator) {
  // Retorna uma nova função que usa "fator" do escopo externo
  return function(numero) {
    return numero * fator;
  };
}

const triplicar = criarMultiplicador(3);
const quadruplicar = criarMultiplicador(4);

console.log(triplicar(5));    // 15
console.log(quadruplicar(5)); // 20
O que acontece aqui?
  • criarMultiplicador(3) executa e retorna uma função que multiplica por 3. Essa função é guardada em triplicar.
  • Quando chamamos triplicar(5), a função interna ainda "lembra" que fator é 3 — mesmo depois de criarMultiplicador ter terminado. Isso é chamado de closure.

Outro exemplo simples para entender melhor:

function criarContador() {
  let contador = 0;         // variável do escopo externo

  return function () {      // função interna — a closure
    contador++;
    console.log(contador);
  };
}
const contarA = criarContador(); // estado A
const contarB = criarContador(); // estado B — separado!

contarA(); // 1
contarA(); // 2
contarB(); // 1  ← começa do zero
contarA(); // 3
          

2. Escopo em JavaScript

Escopo define onde uma variável pode ser acessada no código. Em JavaScript moderno existem três escopos principais:

2.1 Escopo global

Variáveis declaradas fora de qualquer função ou bloco são globais — acessíveis em qualquer lugar do código.

const disciplina = "Programação II"; // variável global

function exibir() {
  // Pode acessar variável global aqui dentro
  console.log(disciplina);
}

exibir(); // Programação II
console.log(disciplina); // Programação II

2.2 Escopo de função

Variáveis declaradas com var dentro de uma função existem apenas dentro dela.

function calcular() {
  var resultado = 42; // existe só dentro de calcular()
  console.log(resultado); // 42
}

calcular();
// console.log(resultado); // ← ERRO: resultado is not defined

2.3 Escopo de bloco (let e const)

Com let e const (introduzidos no ES6ES6 é a 6ª versão da especificação ECMAScript — o padrão oficial que define como o JavaScript deve funcionar. Foi lançada em 2015 e é também chamada de ES2015.), variáveis ficam restritas ao bloco { } em que foram declaradas — seja um if, um for ou qualquer outro bloco.

if (true) {
  let mensagem = "Dentro do bloco";
  const constante = 100;
  console.log(mensagem);  // Dentro do bloco
  console.log(constante); // 100
}

// console.log(mensagem);  // ← ERRO: não existe aqui fora
// console.log(constante); // ← ERRO: não existe aqui fora

// Comparação com var (NÃO recomendado em código moderno):
if (true) {
  var vazado = "var vaza do bloco";
}
console.log(vazado); // "var vaza do bloco" ← comportamento problemático
Regra prática: sempre use const por padrão. Use let quando precisar reatribuir a variável. Evite var em código moderno.

3. Arrow Functions

Introduzidas no ES6, as arrow functions são uma sintaxe mais curta para escrever funções. São muito usadas em callbacksCallback é uma função passada como argumento para outra função, para ser executada em algum momento — geralmente quando uma operação termina ou um evento ocorre. e em código JavaScript moderno.

3.1 Sintaxe básica

// Função tradicional
function somar(a, b) {
  return a + b;
}

// Equivalente como arrow function
const somarArrow = (a, b) => {
  return a + b;
};

// Versão ainda mais curta: retorno implícito (sem chaves e sem return)
const somarCurta = (a, b) => a + b;

console.log(somar(3, 4));       // 7
console.log(somarArrow(3, 4));  // 7
console.log(somarCurta(3, 4));  // 7
Regras da sintaxe curta:
  • Se houver um único parâmetro, os parênteses são opcionais: x => x * 2
  • Se não houver parâmetros, os parênteses são obrigatórios: () => "olá"
  • Se o corpo tiver uma única expressão, as chaves e o return podem ser omitidos — o resultado da expressão é retornado automaticamente.
  • Se o corpo tiver múltiplas linhas, use chaves e return explícito normalmente.

3.2 Exemplos variados de sintaxe

// Um parâmetro: parênteses opcionais
const dobrar = x => x * 2;

// Sem parâmetros: parênteses obrigatórios
const dizerOla = () => "Olá!";

// Múltiplas linhas: chaves e return obrigatórios
const calcularDesconto = (preco, percentual) => {
  const desconto = preco * (percentual / 100);
  return preco - desconto;
};

console.log(dobrar(6));               // 12
console.log(dizerOla());              // Olá!
console.log(calcularDesconto(100, 10)); // 90

3.3 Diferença importante: o this

Arrow functions não possuem seu próprio this — elas herdam o this do contexto em que foram criadas. Isso resolve um problema clássico com funções tradicionais dentro de classes e eventos.

// Em funções tradicionais, "this" dentro do callback
// pode ser diferente do esperado:
function Contador() {
  this.valor = 0;

  // Arrow function: "this" vem do Contador, funciona corretamente
  setInterval(() => {
    this.valor++;
    console.log(this.valor);
  }, 1000);
}

const c = new Contador(); // imprime 1, 2, 3... a cada segundo
Por ora, o mais importante é saber escrever arrow functions com conforto. O comportamento do this ficará mais claro quando trabalharmos com classes e eventos do DOM.

4. Callbacks

Um callback é uma função passada como argumento para outra função, para ser executada em algum momento posterior — quando um evento ocorre, quando uma operação termina, etc.

Analogia: imagine que você faz um pedido em um restaurante e deixa seu número de celular. Quando o pedido ficar pronto, o restaurante Analogia: imagine que você faz um pedido em um restaurante e deixa seu número de celular. Quando o pedido ficar pronto, o restaurante te liga. Você não fica parado esperando — você passa uma "instrução de retorno" (o callback) e continua fazendo outras coisas.

4.1 Callback simples

No exemplo abaixo, a função executar recebe outra função como argumento e a chama internamente.

// "operacao" é um callback: uma função passada como argumento
function executar(operacao) {
  console.log("Antes da operação");
  operacao(); // chama o callback
  console.log("Depois da operação");
}

// Passando uma função declarada
function minhaOperacao() {
  console.log("Executando a operação!");
}

executar(minhaOperacao);
// Antes da operação
// Executando a operação!
// Depois da operação
Repare: passamos minhaOperacao sem os parênteses.
  • minhaOperacao → passa a função em si como valor.
  • minhaOperacao()executa a função imediatamente e passa o resultado.
Essa distinção é fundamental para entender callbacks corretamente.

4.2 Callback com argumentos

Callbacks podem receber argumentos. O chamador decide o que passar quando invocar o callback.

function processarNota(nota, callback) {
  if (nota < 0 || nota > 10) {
    throw new Error("Nota inválida");
  }
  callback(nota);
}

function exibirResultado(nota) {
  const situacao = nota >= 6 ? "Aprovado" : "Reprovado";
  console.log(`Nota: ${nota} → ${situacao}`);
}

processarNota(8.5, exibirResultado); // Nota: 8.5 → Aprovado
processarNota(4.0, exibirResultado); // Nota: 4.0 → Reprovado

4.3 Callback com função anônima

Muitas vezes o callback é escrito diretamente no lugar da chamada, sem precisar de um nome. Esse é o padrão mais comum no código web real.

function processarNota(nota, callback) {
  if (nota < 0 || nota > 10) throw new Error("Nota inválida");
  callback(nota);
}

// Passando uma arrow function anônima diretamente como callback
processarNota(7, (nota) => {
  console.log(`Nota recebida: ${nota}`);
});
// Nota recebida: 7

// Versão ainda mais curta com retorno implícito:
processarNota(5, nota => console.log(`Nota: ${nota}`));
// Nota: 5

4.4 Callback assíncrono — o caso mais comum na web

O uso mais frequente de callbacks na web é em operações que levam tempo: esperar um clique, um timer, uma resposta de rede. O código não trava — o callback só é chamado quando o evento acontece.

// setTimeout: executa o callback após um tempo (em milissegundos)
console.log("Início");

setTimeout(() => {
  console.log("Isso aparece depois de 2 segundos");
}, 2000);

console.log("Fim"); // aparece ANTES do setTimeout

// Saída:
// Início
// Fim
// Isso aparece depois de 2 segundos
Repare que "Fim" aparece antes do callback do setTimeout. Isso mostra o comportamento assíncrono: o JavaScript não espera o timer terminar — ele continua executando o resto do código e, quando o timer dispara, chama o callback. Esse modelo é a base de todo o comportamento assíncrono em JavaScript.

5. Funções de ordem superior

Uma função de ordem superior (Higher-Order Function) é aquela que recebe uma função como argumento e/ou retorna uma função. Você já viu isso nos callbacks — agora vamos ver as funções de ordem superior mais usadas em JavaScript para trabalhar com arrays.

5.1 forEach — percorrer

Executa um callback para cada elemento do array. Não retorna nada.

const estudantes = ["Ana", "João", "Maria", "Pedro"];

estudantes.forEach((estudante, indice) => {
  console.log(`${indice + 1}. ${estudante}`);
});

// 1. Ana
// 2. João
// 3. Maria
// 4. Pedro

5.2 map — transformar

Retorna um novo array com o resultado do callback aplicado a cada elemento. O array original não é alterado.

const notas = [6.0, 8.5, 4.0, 9.2];

// Adiciona 1 ponto em todas as notas
const notasAjustadas = notas.map(nota => nota + 1);

console.log(notas);          // [6, 8.5, 4, 9.2]  ← original intacto
console.log(notasAjustadas); // [7, 9.5, 5, 10.2]

// Transformando em objetos com situação
const resultado = notas.map(nota => ({
  nota,
  situacao: nota >= 6 ? "Aprovado" : "Reprovado"
}));

console.log(resultado);
// [ { nota: 6, situacao: 'Aprovado' },
//   { nota: 8.5, situacao: 'Aprovado' },
//   { nota: 4, situacao: 'Reprovado' },
//   { nota: 9.2, situacao: 'Aprovado' } ]
Por que usar map em vez de forEach?
  • Use forEach quando quiser apenas percorrer o array para fazer algo (ex: imprimir, salvar).
  • Use map quando quiser transformar o array em outro array de mesma quantidade de elementos.

5.3 filter — filtrar

Retorna um novo array contendo apenas os elementos para os quais o callback retornou true.

const notas = [6.0, 8.5, 4.0, 9.2, 3.5, 7.0];

const aprovados = notas.filter(nota => nota >= 6);
const reprovados = notas.filter(nota => nota < 6);

console.log(aprovados);  // [6, 8.5, 9.2, 7]
console.log(reprovados); // [4, 3.5]

5.4 reduce — acumular

Reduz o array a um único valor, acumulando o resultado do callback a cada iteração. É o mais poderoso e o mais complexo dos três.

const notas = [6.0, 8.5, 4.0, 9.2];

// Soma todas as notas
const soma = notas.reduce((acumulador, nota) => acumulador + nota, 0);

console.log(soma); // 27.7

// Calculando a média
const media = soma / notas.length;
console.log(media.toFixed(2)); // 6.93
Como o reduce funciona passo a passo:
  • O segundo argumento (0) é o valor inicial do acumulador.
  • A cada iteração, o callback recebe o acumulador e o elemento atual, e retorna o novo acumulador.
  • Iteração 1: 0 + 6.0 = 6.0
  • Iteração 2: 6.0 + 8.5 = 14.5
  • Iteração 3: 14.5 + 4.0 = 18.5
  • Iteração 4: 18.5 + 9.2 = 27.7 → resultado final

5.5 Encadeamento de métodos

Como map e filter retornam novos arrays, podemos encadear várias operações em sequência.

const estudantes = [
  { nome: "Ana",   nota: 8.5 },
  { nome: "João",  nota: 4.0 },
  { nome: "Maria", nota: 9.2 },
  { nome: "Pedro", nota: 5.5 },
];

// Nomes dos estudantes aprovados, em ordem alfabética
const nomesAprovados = estudantes
  .filter(estudante => estudante.nota >= 6)       // filtra aprovados
  .map(estudante => estudante.nome)               // extrai só o nome
  .sort();                                // ordena alfabeticamente

console.log(nomesAprovados); // ["Ana", "Maria"]

6. Para memorizar!

Exercício 1 – Funções e parâmetros padrão

Crie uma função apresentar que receba nome, curso e de>semestre (padrão: 1) e retorne uma string formatada com template literal. Exemplos de saída esperada:

  • apresentar("Ana", "Eng. Computação", 3)"Ana – Eng. Computação – 3º semestre"
  • apresentar("João", "Eng. Computação")"João – Eng. Computação – 1º semestre"

Desafio: reescreva a função como arrow function.

Exercício 2 – Callbacks e validação

Crie uma função processarIdade que receba um número e um callback. A função deve:

  • Lançar um erro se a idade for negativa ou maior que 120.
  • Chamar o callback com a idade se for válida.

Teste passando callbacks diferentes: um que exibe se a pessoa é maior de idade, outro que calcula o ano de nascimento aproximado.

Exercício 3 – map, filter e reduce

Dado o array de produtos abaixo, resolva cada item usando o método indicado:

const produtos = [
  { nome: "Mouse",    preco: 80,  estoque: 5  },
  { nome: "Teclado",  preco: 150, estoque: 0  },
  { nome: "Monitor",  preco: 900, estoque: 2  },
  { nome: "Headset",  preco: 200, estoque: 8  },
  { nome: "Webcam",   preco: 120, estoque: 0  },
];
  • filter: retorne apenas os produtos com estoque maior que zero.
  • map: aplique 10% de desconto em todos os preços e retorne um novo array com nome e novo preço.
  • reduce: calcule o valor total do estoque (preco × estoque) de todos os produtos.

Desafio: usando encadeamento, calcule o valor total apenas dos produtos disponíveis em estoque.

Exercício 4 – Funções que retornam funções (closure)

Crie uma função criarSaudacao que receba um idioma ("pt", "en" ou "es") e retorne uma função que, ao receber um nome, exiba a saudação no idioma correto. Exemplo:

const saudarEmPortugues = criarSaudacao("pt");
const saudarEmIngles    = criarSaudacao("en");

saudarEmPortugues("Ana");  // Olá, Ana!
saudarEmIngles("Ana");     // Hello, Ana!
Exercício 5 – Pipeline de dados (desafio)

Crie um array com pelo menos 6 objetos de estudantes (nome, nota, turma). Em seguida, usando encadeamento de filter, map e reduce:

  • Filtre apenas os estudantes da turma "ECO".
  • Filtre entre esses apenas os aprovados (nota ≥ 6).
  • Use map para extrair só os nomes.
  • Use reduce para construir uma string final no formato:
    "Aprovados da turma ECO: Ana, Maria, Pedro"

Dica: cada método retorna um array, então você pode chamar o próximo diretamente encadeado.

Roteiro de estudo para quem faltou

  • Leia as seções 1, 2 e 3 com atenção e reproduza cada exemplo em um arquivo funcoes.js, executando com node funcoes.js ou no console do navegador (F12).
  • Modifique os exemplos: troque valores, adicione parâmetros, reescreva funções tradicionais como arrow functions.
  • Leia a seção 4 (Callbacks) com cuidado — é o conceito mais novo e será base para as próximas unidades.
  • Resolva os exercícios 1 a 4. O exercício 5 é desafio opcional.
  • Anote dúvidas para discutirmos na próxima aula.