Desestruturação, spread e rest em JavaScript
Desestruturar objeto por nome e array por posição, renomear, dar valor padrão e usar os três pontos dos dois lados do igual — com saída real no Node 24.
Desestruturação é a sintaxe que extrai valores de objetos ou arrays para
variáveis. O padrão com chaves procura propriedades pelo nome; o padrão com
colchetes lê posições. Os três pontos (...) podem recolher o restante ou
espalhar valores, conforme o contexto.
Pense em desfazer uma mala. No objeto, cada bolso tem etiqueta e você procura
cliente pelo nome, independentemente da ordem. No array, os objetos estão em
divisórias numeradas: o primeiro nome recebe a posição zero. O rest fecha o
que sobrou numa nova mala; o spread abre uma coleção e distribui suas partes
num novo literal ou numa chamada. Tecnicamente, essa distribuição é rasa:
objetos aninhados continuam sendo as mesmas referências.
Assim, const { cliente } = pedido procura uma chave, enquanto const [primeiro] = lista lê o índice 0. À esquerda, ... recolhe; à direita,
espalha.
Todos os exemplos são do mesmo checkout que aparece nas outras lições da trilha: pedido, cliente, carrinho, frete.
Objeto casa por nome, array casa por posição
const pedido = {
numero: 8412,
cliente: 'Ana Souza',
total: 289.9,
};
const { cliente, total } = pedido;
console.log(cliente, total);
const { total: valorTotal, cliente: nomeDoCliente } = pedido;
console.log(nomeDoCliente, valorTotal);
const enderecos = ['Rua das Laranjeiras, 210', 'Av. Paulista, 1000'];
const [principal, alternativo] = enderecos;
console.log(principal);
console.log(alternativo);Repare na segunda desestruturação: eu inverti a ordem das chaves e o resultado foi o mesmo. Num objeto a ordem em que você escreve não significa nada — o que liga o nome ao valor é a chave. Num array, ao contrário, o nome é invenção sua e a posição é tudo.
Isso também explica o total: valorTotal. Dois pontos aqui não é “tipo”,
como em TypeScript: é renomeação. Leia como “pegue a chave total e guarde numa
variável chamada valorTotal”.
Pular posição, recolher o resto e trocar dois valores
No array, uma vírgula sozinha pula um índice, e os três pontos no fim juntam tudo que sobrou:
const parcelas = [96.63, 96.63, 96.64, 0, 0];
const [primeira, , terceira, ...naoCobradas] = parcelas;
console.log(primeira, terceira, naoCobradas);
let freteExpresso = 24.9;
let freteEconomico = 12.5;
[freteExpresso, freteEconomico] = [freteEconomico, freteExpresso];
console.log(freteExpresso, freteEconomico);A troca na última linha é o truque mais conhecido da sintaxe: sem variável temporária, porque o array da direita é montado inteiro antes de qualquer atribuição acontecer.
Valor padrão entra só para undefined
const cliente = { nome: 'Bruno Lima', cidade: 'Recife', cupom: null };
const { nome, cidade, cupom = 'SEM-CUPOM', uf = 'PE' } = cliente;
console.log(nome, cidade);
console.log(cupom, uf);uf não existia no objeto, então o padrão valeu. cupom existia valendo null
— e null é um valor, não uma ausência. O padrão passou longe.
É a mesma regra do parâmetro default que você viu em
funções em JavaScript, e a mesma armadilha: dado que
vem de formulário ou de API costuma chegar como null, não como undefined.
Desestruturar direto no parâmetro
Quando a função recebe um objeto de opções, dá para abrir o objeto na própria assinatura — e a assinatura vira documentação do que a função usa:
function calcularFrete({ peso, regiao = 'sudeste', expresso = false }) {
const tabela = { sudeste: 12.5, sul: 18.9, nordeste: 27.4, norte: 34.9 };
const base = tabela[regiao] ?? 39.9;
return (base + peso * 1.5) * (expresso ? 1.8 : 1);
}
console.log(calcularFrete({ peso: 2 }));
console.log(calcularFrete({ peso: 2, regiao: 'norte' }));
console.log(calcularFrete({ peso: 2, regiao: 'norte', expresso: true }));Quem chama passa um objeto com nomes, então a ordem dos argumentos deixa de
importar e ninguém mais precisa lembrar se expresso vinha antes ou depois de
regiao.
Aninhado: nem todo nome que você escreve vira variável
Desestruturação aninhada segue o formato do objeto. O que muita gente não percebe é que os nós do caminho não ficam disponíveis:
const pedido = {
numero: 8412,
cliente: { nome: 'Ana Souza', endereco: { cidade: 'São Paulo', uf: 'SP' } },
itens: [
{ sku: 'TEC-01', preco: 289.9 },
{ sku: 'MOU-07', preco: 149.9 },
],
};
const {
cliente: { nome, endereco: { cidade, uf } },
itens: [{ sku: primeiroSku }],
} = pedido;
console.log(nome, `${cidade}/${uf}`, primeiroSku);
console.log(typeof cliente, typeof endereco);cliente e endereco são caminho, não destino: só nome, cidade, uf e
primeiroSku viraram variáveis. Se você precisa do objeto e do que está
dentro dele, peça os dois: const { cliente, cliente: { nome } } = pedido.
Rest à esquerda: separar um campo do resto
Nos objetos, os três pontos à esquerda recolhem todas as chaves que você não nomeou. O uso mais honesto disso é tirar um campo sensível de circulação:
const formulario = {
nome: 'Ana Souza',
email: 'ana@exemplo.com.br',
senha: 'nao-vai-para-o-log',
cep: '01310-100',
};
const { senha, ...dadosPublicos } = formulario;
console.log(dadosPublicos);
console.log('tamanho da senha:', senha.length);O objeto original continua intacto. dadosPublicos é novo, e é ele que você
manda para o log ou para a resposta da API.
Spread à direita: copiar, mesclar e espalhar
Do outro lado do igual, os mesmos três pontos derramam o conteúdo dentro de um literal ou de uma chamada:
const padraoDoPedido = { frete: 12.5, cupom: null, embalagemPresente: false };
const pedidoDaAna = { ...padraoDoPedido, frete: 0, cliente: 'Ana Souza' };
console.log(pedidoDaAna);
const carrinho = ['Teclado mecânico', 'Mouse sem fio'];
const carrinhoComBrinde = [...carrinho, 'Adesivo DevClub'];
console.log(carrinho.length, carrinhoComBrinde.length);
console.log(Math.max(...[289.9, 149.9, 1199]));Ordem importa: quem vem depois vence. frete: 0 sobrescreveu o 12.5 que veio
do padrão. Inverta as duas linhas e o padrão é que apaga o valor específico —
esse é o bug de mesclagem mais comum em código de configuração.
Na última linha, Math.max não recebe um array: recebe três argumentos soltos.
Espalhar é justamente transformar coleção em lista de argumentos.
Spread copia um nível — e só um
Aqui mora o mal-entendido caro. Espalhar produz um objeto novo, mas os valores copiados continuam sendo os mesmos objetos de antes:
const original = {
cliente: 'Ana Souza',
endereco: { cidade: 'São Paulo', uf: 'SP' },
};
const copia = { ...original };
copia.cliente = 'Bruno Lima';
copia.endereco.cidade = 'Recife';
console.log('nome no original:', original.cliente);
console.log('cidade no original:', original.endereco.cidade);
console.log('mesmo objeto?', original.endereco === copia.endereco);Trocar o nome não vazou; trocar a cidade vazou. cliente era uma string, e
strings são copiadas por valor. endereco era um objeto, e o que foi copiado
foi o endereço na memória — os dois nomes apontam para a mesma caixa.
Quando você precisa mesmo de independência total, o Node tem structuredClone
embutido desde a versão 17:
const original = {
cliente: 'Ana Souza',
endereco: { cidade: 'São Paulo', uf: 'SP' },
};
const copia = structuredClone(original);
copia.endereco.cidade = 'Recife';
console.log(original.endereco.cidade, copia.endereco.cidade);
console.log('mesmo objeto?', original.endereco === copia.endereco);Erros comuns
Desestruturar de undefined
O clássico: uma lista em que um item não tem o objeto que você esperava.
const pedidos = [
{ numero: 8412, cliente: { nome: 'Ana Souza' } },
{ numero: 8413 },
];
for (const pedido of pedidos) {
const { nome } = pedido.cliente;
console.log(pedido.numero, nome);
}Repare que a primeira volta do laço imprimiu antes de quebrar — o erro é do
segundo pedido. E a mensagem é generosa: ela cita a expressão inteira,
pedido.cliente, não só o nome da variável. Ela está te dizendo onde o caminho
morreu.
A correção mais curta é garantir um objeto para desestruturar:
const pedidos = [
{ numero: 8412, cliente: { nome: 'Ana Souza' } },
{ numero: 8413 },
];
for (const pedido of pedidos) {
const { nome = 'não informado' } = pedido.cliente ?? {};
console.log(pedido.numero, nome);
}O ?? {} entrega um objeto vazio quando cliente não veio, e aí o valor padrão
de nome finalmente tem chance de entrar. Esse par
?./?? é o assunto da lição sobre
optional chaining e nullish coalescing.
Quando o erro é de leitura em vez de desestruturação, a mensagem muda para
Cannot read properties of undefined.
Desestruturar numa variável já declarada
Se as variáveis já existem e você quer só atribuir, a linha precisa de parênteses. Sem eles, o JavaScript lê a chave de abertura como início de um bloco de código:
let cidade;
let uf;
const endereco = { cidade: 'São Paulo', uf: 'SP' };
{ cidade, uf } = endereco;
console.log(cidade, uf);Com os parênteses, a mesma linha vira expressão e funciona:
let cidade;
let uf;
const endereco = { cidade: 'São Paulo', uf: 'SP' };
({ cidade, uf } = endereco);
console.log(cidade, uf);Na prática você raramente precisa disso — quase sempre é sinal de que essas
variáveis deveriam ter nascido como const na
linha da desestruturação.
Quando usar cada um
| situação | escreva | por quê |
|---|---|---|
| ler 2 a 4 campos de um objeto | const { a, b } = obj |
encurta o corpo e mostra logo o que a função usa |
| função com opções | function f({ a, b = 1 }) |
a chamada fica autoexplicativa e a ordem some |
| tirar um campo do objeto | const { senha, ...resto } = obj |
não muta o original e o nome do que sobrou é seu |
| juntar padrão com específico | { ...padrao, ...especifico } |
quem vem depois vence; leia sempre da esquerda |
| passar array como argumentos | f(...lista) |
evita apply e mantém a chamada legível |
| copiar objeto com objeto dentro | structuredClone(obj) |
spread copiaria a referência interna |
Duas regras que economizam discussão em revisão de código: não desestruture o
que você usa uma vez só — pedido.cliente.nome numa linha isolada é mais
legível que três linhas de padrão; e não aninhe mais de dois níveis, porque
a partir daí o padrão fica maior que o código que ele economiza.
Crie um objeto pedido com cliente, senha e endereco. Separe senha com
rest, renomeie cliente e faça uma cópia por spread. Altere uma propriedade
dentro de endereco na cópia e confirme que o original também muda; depois
repita com structuredClone e confirme a independência. O contraste torna
visível o limite da cópia rasa.
A próxima lição da trilha de JavaScript resolve justamente o buraco que sobrou aqui: como ler um caminho fundo sem estourar quando um pedaço do meio não existe. O guia completo de JavaScript mostra onde essa lição cai na ordem de estudo.
Perguntas frequentes
Desestruturação cria cópia ou referência?
Por que os três pontos às vezes se chamam rest e às vezes spread?
Posso desestruturar de qualquer coisa?
Vale a pena desestruturar dentro do parâmetro da função?
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 — Atribuição via desestruturação — developer.mozilla.org
- MDN — Sintaxe de espalhamento (spread) — developer.mozilla.org


