Function expression vs declaration em JavaScript
As duas formas de criar função em JavaScript não são intercambiáveis: hoisting, nome na stack trace e IIFE, com o erro de cada escolha rodando no Node 24.
Existem duas formas de criar uma função com a palavra function, e a diferença
entre elas não é estética. Function declaration começa a linha com
function e é içada inteira para o topo do escopo — dá para chamar antes de
escrever. Function expression guarda a função numa variável e só existe a
partir da linha da atribuição.
Na prática: declaration você chama de qualquer lugar do escopo; expression só
depois. Chamar antes gera um erro diferente conforme a variável seja var,
let ou const — e os três aparecem reproduzidos aqui, com o rastro real do
Node 24.16.0.
Todos os exemplos usam a mesma loja virtual das lições anteriores da trilha de JavaScript: subtotal, desconto, carrinho.
O comportamento recebe dois nomes precisos: function declaration e function expression. Em palavras simples, uma já é registrada com a função pronta; a outra depende de a execução chegar à atribuição da variável.
A loja que abre cedo e o balcão montado depois
Imagine uma loja cujo endereço já consta no mapa quando o dia começa, enquanto
um balcão promocional só passa a existir depois que a equipe o monta. A
declaration se parece com a loja: o escopo registra nome e função antes de rodar
a primeira linha. A expression se parece com o balcão: a variável segue as
regras de var, let ou const e só recebe a função na atribuição.
Antes de executar os dois primeiros casos, faça uma tabela com “nome existe?”, “valor atual” e “pode chamar?”. Preencha cada coluna para a linha anterior à declaração e compare com a saída. Isso mostra por que os erros mudam sem tratar hoisting como mágica.
function calcularSubtotal(preco, quantidade) {
return preco * quantidade;
}
const calcularDesconto = function (valor, percentual) {
return valor * (percentual / 100);
};
console.log(calcularSubtotal(89.9, 3).toFixed(2));
console.log(calcularDesconto(269.7, 15).toFixed(2));
console.log(typeof calcularSubtotal, '|', typeof calcularDesconto);
console.log(calcularSubtotal.name, '|', calcularDesconto.name);Depois de criadas, as duas são indistinguíveis: mesmo typeof, mesmo jeito de
chamar, e até o mesmo .name — o motor deduz o nome da expression a partir da
variável que a recebeu. A separação existe antes dessas linhas.
Repare no ponto e vírgula depois da chave de fechamento da expression. Ele não é
decorativo: aquilo é uma instrução de atribuição, como
const total = 10;. A declaration não leva ponto e vírgula, porque não é
atribuição nenhuma.
Hoisting: a diferença que aparece antes da linha
Uma declaration é registrada no escopo antes de qualquer linha rodar. Chamar em cima da definição funciona:
console.log(calcularSubtotal(89.9, 3).toFixed(2));
function calcularSubtotal(preco, quantidade) {
return preco * quantidade;
}Com expression, o que é içado é a variável, não a função. Guardada em var,
a variável existe desde o topo valendo undefined:
console.log(calcularDesconto(269.7, 15));
var calcularDesconto = function (valor, percentual) {
return valor * (percentual / 100);
};TypeError: calcularDesconto is not a function at file:///private/tmp/loja/expr-var.mjs:1:13 at ModuleJob.run (node:internal/modules/esm/module_job:439:25)
Node.js v24.16.0
Leia a mensagem ao pé da letra: calcularDesconto existe, e o que ela vale
não é chamável. Vale undefined, e o motor está reclamando de você ter posto
parênteses depois de um undefined.
Trocando var por const, o diagnóstico melhora:
console.log(calcularDesconto(269.7, 15));
const calcularDesconto = function (valor, percentual) {
return valor * (percentual / 100);
};ReferenceError: Cannot access ‘calcularDesconto’ before initialization at file:///private/tmp/loja/expr-const.mjs:1:13 at ModuleJob.run (node:internal/modules/esm/module_job:439:25)
Node.js v24.16.0
Agora o erro diz exatamente o que aconteceu: você chegou cedo demais. É a zona
morta temporal de let e const, o mesmo mecanismo detalhado em
variáveis em JavaScript.
Nomeada ou anônima: o que muda na stack trace
Uma expression pode ter nome próprio, escrito entre function e os parênteses.
Esse nome não vaza para fora — ele só serve dentro da própria função e nas
mensagens de erro. E é aí que ele paga o investimento:
const validarAnonima = function (pedido) {
return pedido.itens.length;
};
const validarNomeada = function contarItensDoPedido(pedido) {
return pedido.itens.length;
};
console.log('name:', JSON.stringify(validarAnonima.name), '|', JSON.stringify(validarNomeada.name));
for (const fn of [validarAnonima, validarNomeada]) {
try {
fn({});
} catch (e) {
console.log(e.stack.split('\n')[1].trim());
}
}No primeiro caso o motor emprestou o nome da variável, e isso funciona porque a
função foi atribuída a um const. Quando você passa a função direto como
argumento — itens.map(function (item) { ... }) — não existe variável para
emprestar nome nenhum, e o rastro sai com <anonymous>. Em um map aninhado em
outro map, três <anonymous> seguidos custam caro.
O nome também permite a função chamar a si mesma sem depender da variável de fora:
const somarItens = function somar(itens, i = 0) {
if (i >= itens.length) return 0;
return itens[i].preco + somar(itens, i + 1);
};
const carrinho = [
{ nome: 'Teclado mecânico', preco: 289.9 },
{ nome: 'Mouse sem fio', preco: 149.9 },
{ nome: 'Mousepad', preco: 39.9 },
];
console.log(somarItens(carrinho).toFixed(2));
console.log('o nome interno vaza para fora?', typeof somar);somar funciona dentro da função e não existe fora dela. Se a recursão
dependesse de somarItens, bastaria alguém reatribuir essa variável em outro
ponto do arquivo para a recursão apontar para o lugar errado.
IIFE: o padrão que existia antes dos módulos
Uma expression pode ser chamada na hora em que é criada. Envolva a função em
parênteses e ponha () no fim: é a Immediately Invoked Function Expression.
var precoFinal = (function () {
var taxa = 0.18;
var base = 289.9;
return base * (1 + taxa);
})();
console.log(precoFinal.toFixed(2));
console.log('taxa vazou?', typeof taxa);Os parênteses de fora não são enfeite: sem eles, a linha começaria com a palavra
function e o motor leria uma declaration — que não pode ser chamada na mesma
linha. Os parênteses forçam o contexto de expressão.
Antes de 2015 não havia let, const nem módulos ES: tudo que você declarasse
num arquivo .js de página caía no mesmo escopo global, e dois scripts com uma
variável total brigavam. A IIFE era a única forma barata de fechar uma caixa.
Da mesma ideia nasceu o module pattern, que ainda aparece em bibliotecas
antigas:
const carrinho = (function () {
const itens = [];
return {
adicionar(item) {
itens.push(item);
return itens.length;
},
total() {
return itens.reduce((soma, item) => soma + item.preco, 0);
},
};
})();
console.log(carrinho.adicionar({ nome: 'Teclado mecânico', preco: 289.9 }));
console.log(carrinho.adicionar({ nome: 'Mouse sem fio', preco: 149.9 }));
console.log(carrinho.total().toFixed(2));
console.log('itens por fora:', carrinho.itens);itens não é acessível de fora e mesmo assim os dois métodos continuam
enxergando o array. Isso é closure, e é o
assunto de uma lição inteira mais adiante nesta trilha.
Expression brilha quando a função é uma escolha
Como expression é um valor, ela participa de if, de objeto e de array. Uma
declaration não faz isso — ela é fixa no escopo.
const ambiente = process.env.NODE_ENV ?? 'desenvolvimento';
let registrarEvento;
if (ambiente === 'producao') {
registrarEvento = function enviarParaAnalytics(nome) {
return `analytics: ${nome}`;
};
} else {
registrarEvento = function imprimirNoConsole(nome) {
return `console: ${nome}`;
};
}
console.log(registrarEvento('checkout_iniciado'));
console.log('quem atendeu:', registrarEvento.name);Aqui os nomes internos se pagam sozinhos: o log diz qual das duas
implementações respondeu. Com funções anônimas, registrarEvento.name sairia
vazio nos dois casos e o log não valeria nada.
Erros comuns: chamar a expression cedo demais
O erro característico desta lição já apareceu acima em forma pura, mas na vida
real ele vem disfarçado — dentro de um export, de um setTimeout ou de um
arquivo de configuração lido de cima para baixo:
inicializarLoja();
const conectarBanco = function () {
return 'conectado';
};
function inicializarLoja() {
return conectarBanco();
}ReferenceError: Cannot access ‘conectarBanco’ before initialization at inicializarLoja (file:///private/tmp/loja/init-loja.mjs:8:3) at file:///private/tmp/loja/init-loja.mjs:1:1 at ModuleJob.run (node:internal/modules/esm/module_job:439:25)
Node.js v24.16.0
inicializarLoja é uma declaration e por isso pôde ser chamada na linha 1 sem
reclamação. O problema é que, quando ela rodou, conectarBanco ainda não tinha
sido inicializada. O rastro entrega isso de graça: a linha 1 chamou, a linha 8
quebrou.
Duas correções, e a segunda é melhor. A rápida: mover inicializarLoja() para o
fim do arquivo. A definitiva: transformar conectarBanco em declaration
também — aí a ordem das linhas para de importar.
Qual usar em cada situação
| situação | forma | por quê |
|---|---|---|
| função principal do módulo | declaration | pode ficar no fim do arquivo e ser chamada em cima |
callback passado para map, filter, setTimeout |
expression (em geral arrow) | é um valor de uso único; nomear ajuda no rastro |
| implementação escolhida em tempo de execução | expression | função como valor entra em if, objeto e array |
| método dentro de objeto ou classe | sintaxe curta de método | precisa do this da chamada |
| isolar escopo em script sem bundler | IIFE | código legado; em módulo ES é desnecessário |
A regra que eu passo em aula: se você conseguiria explicar a função pelo nome dela numa conversa — “essa é a que calcula o frete” — ela merece ser uma declaration, com nome fixo e posição livre no arquivo. Se ela é um detalhe de uma linha dentro de outra operação, é expression.
A próxima lição pega a forma mais comum de expression no código moderno e mostra o que ela ganhou e o que perdeu: arrow function em JavaScript. Para ver onde esta lição se encaixa no todo, o guia completo de JavaScript tem o mapa da trilha.
Perguntas frequentes
Qual das duas formas eu devo usar no dia a dia?
Function expression é a mesma coisa que arrow function?
Vale a pena nomear uma function expression?
IIFE ainda faz sentido com módulos ES?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
Todo o código deste artigo foi executado em Node 24.16.0, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- MDN — function expression — developer.mozilla.org
- MDN — IIFE — developer.mozilla.org
- ECMAScript 2026 — Function Definitions — tc39.es


