Funções Avançadas em JavaScript
Até agora, você aprendeu sobre funções tradicionais em JavaScript - como declará-las, passar parâmetros e retornar valores. Agora, vamos explorar formas mais modernas e flexíveis de trabalhar com funções: as arrow functions e o conceito de callbacks. Estes conceitos expandirão significativamente suas possibilidades como programador.
Arrow Functions: Uma Sintaxe Mais Elegante
O Que São Arrow Functions?
Arrow functions (funções seta) são uma forma mais concisa de escrever funções em JavaScript. Elas foram introduzidas no ES6 (ECMAScript 2015) e rapidamente se tornaram populares pela sua sintaxe limpa e clara.
Sintaxe Básica
Vamos comparar uma função tradicional com sua versão arrow function:
// Função tradicional
function somar(a, b) {
return a + b;
}
// Arrow function equivalente
const somar = (a, b) => {
return a + b;
};
// Arrow function concisa (sem chaves e return implícito)
const somar = (a, b) => a + b;
Observe as diferenças:
- Não usamos a palavra-chave
function - Usamos uma "seta"
=>entre os parâmetros e o corpo da função - Se a função tem apenas uma expressão, podemos omitir as chaves
{}e oreturn
Casos Especiais de Sintaxe
Um Único Parâmetro
Quando a função tem apenas um parâmetro, podemos omitir os parênteses:
// Com parênteses
const dobrar = (numero) => numero * 2;
// Sem parênteses (mais conciso)
const dobrar = numero => numero * 2;
console.log(dobrar(5)); // 10
Sem Parâmetros
Se a função não tem parâmetros, os parênteses vazios são obrigatórios:
const saudacao = () => "Olá, mundo!";
console.log(saudacao()); // "Olá, mundo!"
Retornando Objetos
Para retornar um objeto literal diretamente, envolva-o em parênteses:
// Incorreto - JavaScript confunde as chaves do objeto com as chaves da função
const criarPessoa = nome => { nome: nome }; // ❌ Erro!
// Correto - usando parênteses
const criarPessoa = nome => ({ nome: nome });
console.log(criarPessoa("Maria")); // { nome: "Maria" }
Arrow Functions Multilinha
Quando precisamos de múltiplas instruções, usamos chaves e o return explícito:
const calcularMedia = (numeros) => {
let soma = 0;
for (let numero of numeros) {
soma += numero;
}
return soma / numeros.length;
};
const notas = [7.5, 8.0, 6.5, 9.0];
console.log(calcularMedia(notas)); // 7.75
Quando Usar Arrow Functions?
Use arrow functions quando:
- A função é simples e curta
- A função é passada como callback (veremos a seguir)
- Você quer código mais conciso e legível
Use funções tradicionais quando:
- A função é complexa e longa
- Você precisa de comportamentos especiais do
this(conceito avançado) - A função precisa ter um nome claro para facilitar depuração
Funções Como Valores: Cidadãos de Primeira Classe
Conceito Fundamental
Uma das características mais poderosas do JavaScript é que funções são valores. Isso significa que podemos:
- Atribuir funções a variáveis
- Passar funções como argumentos para outras funções
- Retornar funções de outras funções
- Armazenar funções em arrays ou objetos
Esta característica faz com que funções sejam "cidadãos de primeira classe" (first-class citizens) em JavaScript.
Exemplos Práticos
// 1. Funções em variáveis
const saudacao = function(nome) {
return `Olá, ${nome}!`;
};
// 2. Funções em arrays
const operacoes = [
(x) => x + 10,
(x) => x * 2,
(x) => x - 5
];
let valor = 5;
for (let operacao of operacoes) {
valor = operacao(valor);
console.log(valor); // 15, 30, 25
}
// 3. Funções em objetos
const calculadora = {
somar: (a, b) => a + b,
subtrair: (a, b) => a - b,
multiplicar: (a, b) => a * b
};
console.log(calculadora.somar(5, 3)); // 8
Callbacks: Funções Que Esperam Sua Vez
O Que São Callbacks?
Um callback é uma função que você passa como argumento para outra função, esperando que ela seja executada ("chamada de volta") em algum momento específico.
É como dar instruções a alguém dizendo: "Quando você terminar isso, faça aquilo que eu te disser".
Exemplo Simples
function processarNumero(numero, operacao) {
console.log("Processando o número...");
const resultado = operacao(numero);
console.log("Resultado:", resultado);
return resultado;
}
// Usando diferentes callbacks
processarNumero(5, numero => numero * 2);
// Processando o número...
// Resultado: 10
processarNumero(5, numero => numero + 100);
// Processando o número...
// Resultado: 105
Callbacks com Arrays
Os métodos de array que você aprendeu anteriormente usam callbacks extensivamente:
const numeros = [1, 2, 3, 4, 5];
// forEach - executa um callback para cada elemento
numeros.forEach(numero => {
console.log(`O número é: ${numero}`);
});
// map - transforma cada elemento usando o callback
const dobrados = numeros.map(numero => numero * 2);
console.log(dobrados); // [2, 4, 6, 8, 10]
// filter - mantém elementos que passam no teste do callback
const pares = numeros.filter(numero => numero % 2 === 0);
console.log(pares); // [2, 4]
// find - retorna o primeiro elemento que passa no teste
const maiorQueTres = numeros.find(numero => numero > 3);
console.log(maiorQueTres); // 4
Callback Personalizado - Exemplo Prático
Vamos criar uma função que processa uma lista de tarefas:
function processarTarefas(tarefas, callback) {
console.log("Iniciando processamento...");
for (let tarefa of tarefas) {
callback(tarefa);
}
console.log("Processamento concluído!");
}
const minhasTarefas = [
"Estudar JavaScript",
"Fazer exercícios",
"Revisar código"
];
// Usando diferentes callbacks
processarTarefas(minhasTarefas, tarefa => {
console.log(`✓ ${tarefa}`);
});
processarTarefas(minhasTarefas, tarefa => {
console.log(`Tarefa: ${tarefa} - Concluída!`);
});
Callbacks com Múltiplos Parâmetros
Callbacks podem receber múltiplos argumentos:
function processarProdutos(produtos, callback) {
for (let i = 0; i < produtos.length; i++) {
// Passa o produto e seu índice para o callback
callback(produtos[i], i);
}
}
const produtos = ["Notebook", "Mouse", "Teclado"];
processarProdutos(produtos, (produto, indice) => {
console.log(`${indice + 1}. ${produto}`);
});
// 1. Notebook
// 2. Mouse
// 3. Teclado
Higher-Order Functions: Funções que Trabalham com Funções
O Que São?
Higher-order functions (funções de ordem superior) são funções que:
- Recebem outras funções como argumentos, OU
- Retornam funções como resultado
Funções que Recebem Funções
Você já viu exemplos com callbacks. Aqui está outro exemplo prático:
function aplicarOperacao(array, operacao) {
const resultado = [];
for (let item of array) {
resultado.push(operacao(item));
}
return resultado;
}
const numeros = [1, 2, 3, 4, 5];
// Diferentes operações
const quadrados = aplicarOperacao(numeros, x => x * x);
console.log(quadrados); // [1, 4, 9, 16, 25]
const incrementados = aplicarOperacao(numeros, x => x + 10);
console.log(incrementados); // [11, 12, 13, 14, 15]
Funções que Retornam Funções
Uma função pode criar e retornar outra função:
function criarMultiplicador(fator) {
return function(numero) {
return numero * fator;
};
}
// Criando funções especializadas
const duplicar = criarMultiplicador(2);
const triplicar = criarMultiplicador(3);
const quintuplicar = criarMultiplicador(5);
console.log(duplicar(10)); // 20
console.log(triplicar(10)); // 30
console.log(quintuplicar(10)); // 50
Por que isso é útil?
- Você cria funções personalizadas dinamicamente
- Evita repetição de código
- Torna o código mais flexível e reutilizável
Exemplo Prático Completo
function criarValidador(tipoValidacao) {
if (tipoValidacao === "email") {
return (texto) => texto.includes("@") && texto.includes(".");
}
if (tipoValidacao === "telefone") {
return (texto) => texto.length >= 10 && /^\d+$/.test(texto);
}
if (tipoValidacao === "naoVazio") {
return (texto) => texto.trim().length > 0;
}
return () => true; // Validador padrão que sempre passa
}
// Criando validadores específicos
const validarEmail = criarValidador("email");
const validarTelefone = criarValidador("telefone");
const validarNome = criarValidador("naoVazio");
console.log(validarEmail("teste@email.com")); // true
console.log(validarEmail("invalido")); // false
console.log(validarTelefone("1234567890")); // true
console.log(validarTelefone("12-345")); // false
Closures: Memória das Funções
O Que São Closures?
Um closure ocorre quando uma função "lembra" das variáveis do escopo onde foi criada, mesmo depois de sair desse escopo.
É como se a função levasse uma "mochila" com as variáveis que ela precisa.
Exemplo Básico
function criarContador() {
let contador = 0; // Variável "privada"
return function() {
contador++;
return contador;
};
}
const meuContador = criarContador();
console.log(meuContador()); // 1
console.log(meuContador()); // 2
console.log(meuContador()); // 3
// Criando outro contador independente
const outroContador = criarContador();
console.log(outroContador()); // 1
console.log(outroContador()); // 2
O que aconteceu?
- A função retornada "lembra" da variável
contador - Cada vez que criamos um novo contador, ele tem seu próprio
contadorindependente - A variável
contadornão pode ser acessada diretamente de fora
Exemplo Prático: Configurador de Mensagens
function criarFormatadorMensagem(prefixo) {
return function(mensagem) {
return `${prefixo}: ${mensagem}`;
};
}
const logInfo = criarFormatadorMensagem("[INFO]");
const logErro = criarFormatadorMensagem("[ERRO]");
const logAviso = criarFormatadorMensagem("[AVISO]");
console.log(logInfo("Sistema iniciado")); // [INFO]: Sistema iniciado
console.log(logErro("Falha na conexão")); // [ERRO]: Falha na conexão
console.log(logAviso("Memória em 80%")); // [AVISO]: Memória em 80%
Closure com Múltiplas Funções
function criarCarteira(saldoInicial) {
let saldo = saldoInicial;
return {
depositar: function(valor) {
saldo += valor;
return `Depósito de R$ ${valor}. Saldo atual: R$ ${saldo}`;
},
sacar: function(valor) {
if (valor > saldo) {
return "Saldo insuficiente!";
}
saldo -= valor;
return `Saque de R$ ${valor}. Saldo atual: R$ ${saldo}`;
},
consultarSaldo: function() {
return `Saldo atual: R$ ${saldo}`;
}
};
}
const minhaCarteira = criarCarteira(100);
console.log(minhaCarteira.consultarSaldo()); // Saldo atual: R$ 100
console.log(minhaCarteira.depositar(50)); // Depósito de R$ 50. Saldo atual: R$ 150
console.log(minhaCarteira.sacar(30)); // Saque de R$ 30. Saldo atual: R$ 120
console.log(minhaCarteira.sacar(200)); // Saldo insuficiente!
Diferenças Entre Tipos de Função
Comparação Rápida
| Aspecto | Função Declarada | Expressão de Função | Arrow Function |
|---------|------------------|---------------------|----------------|
| Sintaxe | function nome() {} | const nome = function() {} | const nome = () => {} |
| Hoisting | Sim | Não | Não |
| Nome | Sempre tem | Pode ser anônima | Sempre anônima |
| Uso como método | ✓ | ✓ | Limitado |
| Concisão | Moderada | Moderada | Alta |
| Contexto this | Próprio | Próprio | Herda do pai |
Quando Usar Cada Uma?
Função Declarada:
function calcularTotal(itens) {
// Boa para funções principais e nomeadas
// Pode ser chamada antes da definição (hoisting)
}
Expressão de Função:
const calcularDesconto = function(valor, percentual) {
// Boa quando você quer controlar quando a função é definida
// Útil em estruturas condicionais
};
Arrow Function:
const aplicarDesconto = (valor, desconto) => valor * (1 - desconto);
// Ótima para callbacks e funções curtas
// Sintaxe mais limpa e moderna
Exemplos Práticos Integrados
Sistema de Processamento de Pedidos
function criarSistemaPedidos() {
const pedidos = [];
return {
adicionar: (pedido) => {
pedidos.push({
...pedido,
id: pedidos.length + 1,
data: new Date()
});
return "Pedido adicionado!";
},
listar: () => pedidos,
buscarPor: (criterio) => {
return pedidos.filter(criterio);
},
processar: (callback) => {
pedidos.forEach((pedido, indice) => {
callback(pedido, indice);
});
}
};
}
const sistema = criarSistemaPedidos();
// Adicionando pedidos
sistema.adicionar({ produto: "Notebook", valor: 3000 });
sistema.adicionar({ produto: "Mouse", valor: 50 });
sistema.adicionar({ produto: "Teclado", valor: 200 });
// Buscando pedidos caros (usando callback)
const pedidosCaros = sistema.buscarPor(pedido => pedido.valor > 100);
console.log(pedidosCaros);
// Processando todos os pedidos (usando callback)
sistema.processar((pedido, indice) => {
console.log(`${indice + 1}. ${pedido.produto} - R$ ${pedido.valor}`);
});
Sistema de Filtros de Produtos
function criarFiltrosProdutos(produtos) {
return {
porPreco: (min, max) => {
return produtos.filter(p => p.preco >= min && p.preco <= max);
},
porCategoria: (categoria) => {
return produtos.filter(p => p.categoria === categoria);
},
comDesconto: () => {
return produtos.filter(p => p.desconto > 0);
},
ordenar: (criterio) => {
const copia = [...produtos];
return copia.sort(criterio);
}
};
}
const produtos = [
{ nome: "Notebook", preco: 3000, categoria: "Eletrônicos", desconto: 10 },
{ nome: "Cadeira", preco: 500, categoria: "Móveis", desconto: 0 },
{ nome: "Mouse", preco: 50, categoria: "Eletrônicos", desconto: 5 }
];
const filtros = criarFiltrosProdutos(produtos);
console.log(filtros.porPreco(100, 1000));
console.log(filtros.porCategoria("Eletrônicos"));
console.log(filtros.comDesconto());
console.log(filtros.ordenar((a, b) => a.preco - b.preco));
Próximmos passos
Na próxima aula nos aprofundaremos no universo dos Arrays. Você aprenderá:
- Como criar e usar arrays
- Sobre métodos e propriedades importantes
- Como percorrer por todos os dados de um array
- A mutabilidade dos Arrays
Continue praticando! A compreensão profunda desses conceitos virá com a aplicação prática em diferentes situações.