Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIniciantecódigo testado

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.

Rodolfo Mori7 min de leitura

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

js
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);
Ana Souza 289.9 Ana Souza 289.9 Rua das Laranjeiras, 210 Av. Paulista, 1000

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:

js
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);
96.63 96.64 [ 0, 0 ] 12.5 24.9

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

js
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);
Bruno Lima Recife null PE

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:

js
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 }));
15.5 37.9 68.22

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:

js
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);
Ana Souza São Paulo/SP TEC-01 undefined undefined

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:

js
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);
{ nome: 'Ana Souza', email: 'ana@exemplo.com.br', cep: '01310-100' } tamanho da senha: 18

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:

js
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]));
{ frete: 0, cupom: null, embalagemPresente: false, cliente: 'Ana Souza' } 2 3 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:

js
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);
nome no original: Ana Souza cidade no original: Recife mesmo objeto? true

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:

js
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);
São Paulo Recife mesmo objeto? false

Erros comuns

Desestruturar de undefined

O clássico: uma lista em que um item não tem o objeto que você esperava.

js
const pedidos = [
  { numero: 8412, cliente: { nome: 'Ana Souza' } },
  { numero: 8413 },
];

for (const pedido of pedidos) {
  const { nome } = pedido.cliente;
  console.log(pedido.numero, nome);
}
8412 Ana Souza file:///private/tmp/checkout/pedido-sem-cliente.mjs:7 const { nome } = pedido.cliente; ^ TypeError: Cannot destructure property 'nome' of 'pedido.cliente' as it is undefined. at file:///private/tmp/checkout/pedido-sem-cliente.mjs:7:11 Node.js v24.16.0

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:

js
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);
}
8412 Ana Souza 8413 não informado

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:

js
let cidade;
let uf;
const endereco = { cidade: 'São Paulo', uf: 'SP' };

{ cidade, uf } = endereco;

console.log(cidade, uf);
file:///private/tmp/checkout/atribuir-sem-parenteses.mjs:5 { cidade, uf } = endereco; ^ SyntaxError: Unexpected token '=' Node.js v24.16.0

Com os parênteses, a mesma linha vira expressão e funciona:

js
let cidade;
let uf;
const endereco = { cidade: 'São Paulo', uf: 'SP' };

({ cidade, uf } = endereco);

console.log(cidade, uf);
São Paulo SP

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.

  • desestruturacao
  • spread
  • rest
  • objetos
  • arrays

Perguntas frequentes

Desestruturação cria cópia ou referência?
Ela copia o valor da propriedade. Se o valor é um número ou uma string, você fica com uma cópia independente. Se é um objeto ou um array, o que foi copiado é a referência — mexer nele mexe no original.
Por que os três pontos às vezes se chamam rest e às vezes spread?
É o mesmo símbolo em dois papéis. À esquerda do igual, num padrão de desestruturação ou numa lista de parâmetros, ele recolhe o que sobrou (rest). À direita, dentro de um literal ou de uma chamada, ele espalha o conteúdo item a item (spread).
Posso desestruturar de qualquer coisa?
De qualquer valor que não seja null nem undefined. Desestruturação por posição ainda exige que o valor seja iterável — objeto comum não é, e tentar usar colchetes nele dá "is not iterable".
Vale a pena desestruturar dentro do parâmetro da função?
Vale quando a função recebe um objeto de opções com três ou mais campos: a assinatura passa a documentar o que a função usa. Não vale quando o objeto é a entidade inteira e você precisa repassá-la adiante.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

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

  1. MDN — Atribuição via desestruturação — developer.mozilla.org
  2. MDN — Sintaxe de espalhamento (spread) — developer.mozilla.org

Continue por aqui