Desestruturação, Spread e Rest Operator

[37] Desestruturação, Spread e Rest Operator

Os mesmos três pontinhos espalham num lugar e recolhem no outro: espalhando, juntam dois arrays; recolhendo, transformam argumentos soltos num array só. Junto vêm a desestruturação com renome e valor padrão, o parâmetro que já se abre na assinatura, e a ordem que decide quem vence num conflito.
Javascript

12 min de leitura

Se você já leu código moderno em JavaScript — especialmente em projetos React ou Node — provavelmente encontrou sintaxes como const { nome, idade } = usuario ou ...props e ficou em dúvida sobre o que significam.

Esses são três recursos introduzidos no ES6 que mudaram completamente a forma de escrever JavaScript: desestruturação, spread e rest. Eles não adicionam nada que você não pudesse fazer antes — mas tornam o código dramaticamente mais limpo e legível.

Desestruturação de Objetos

Sem desestruturação, para extrair propriedades de um objeto você fazia assim:

const usuario = {
  nome: "Fernanda",
  idade: 29,
  cidade: "Recife",
};

// Jeito antigo — repetitivo
const nome = usuario.nome;
const idade = usuario.idade;
const cidade = usuario.cidade;

Com desestruturação:

const { nome, idade, cidade } = usuario;

console.log(nome);   // Fernanda
console.log(idade);  // 29
console.log(cidade); // Recife

Muito mais limpo. Você declara as variáveis e extrai os valores do objeto em uma única linha.

Renomeando variáveis na desestruturação

E se o nome da propriedade não for um bom nome de variável, ou já existir no escopo?

const produto = {
  nome: "Notebook",
  preco: 3500,
};

// Renomeando: chave: novoNome
const { nome: nomeProduto, preco: valorProduto } = produto;

console.log(nomeProduto);   // Notebook
console.log(valorProduto);  // 3500

Valores padrão na desestruturação

Se a propriedade não existir no objeto, você pode definir um valor padrão:

const config = {
  tema: "escuro",
};

const { tema, idioma = "pt-BR", notificacoes = true } = config;

console.log(tema);          // escuro
console.log(idioma);        // pt-BR  ← valor padrão
console.log(notificacoes);  // true   ← valor padrão

Desestruturação em funções

Este é o uso mais comum no dia a dia — especialmente em React:

// Sem desestruturação
function exibirUsuario(usuario) {
  console.log(`${usuario.nome}, ${usuario.idade} anos`);
}

// Com desestruturação no parâmetro
function exibirUsuario({ nome, idade }) {
  console.log(`${nome}, ${idade} anos`);
}

exibirUsuario({ nome: "Pedro", idade: 34 }); // Pedro, 34 anos

Muito mais legível — você vê exatamente quais propriedades a função precisa.

Desestruturação de objetos aninhados

const pedido = {
  id: 1042,
  cliente: {
    nome: "Lucia",
    email: "lucia@email.com",
  },
  total: 450,
};

const {
  id,
  cliente: { nome, email },
  total,
} = pedido;

console.log(id);    // 1042
console.log(nome);  // Lucia
console.log(email); // lucia@email.com
console.log(total); // 450

Desestruturação de Arrays

Arrays também podem ser desestruturados — a diferença é que você usa posição, não nome:

const cores = ["vermelho", "verde", "azul"];

const [primeira, segunda, terceira] = cores;

console.log(primeira); // vermelho
console.log(segunda);  // verde
console.log(terceira); // azul

Você pode pular elementos usando vírgulas:

const numeros = [10, 20, 30, 40, 50];

const [primeiro, , terceiro, , quinto] = numeros;

console.log(primeiro); // 10
console.log(terceiro); // 30
console.log(quinto);   // 50

Troca de variáveis com desestruturação

Um truque elegante que elimina a necessidade de uma variável temporária:

let a = 1;
let b = 2;

// Jeito antigo
let temp = a;
a = b;
b = temp;

// Com desestruturação — muito mais limpo
[a, b] = [b, a];

console.log(a); // 2
console.log(b); // 1

Spread Operator — espalhando dados

O operador ... usado como spread espalha os elementos de um array ou as propriedades de um objeto:

Com arrays:

const frutas = ["maçã", "banana"];
const maisFrutas = ["laranja", "uva"];

// Combinando arrays
const todasFrutas = [...frutas, ...maisFrutas];
console.log(todasFrutas); // ["maçã", "banana", "laranja", "uva"]

// Adicionando elementos
const comKiwi = [...frutas, "kiwi"];
console.log(comKiwi); // ["maçã", "banana", "kiwi"]

// Copiando array (sem referência compartilhada)
const copia = [...frutas];

Com objetos:

const endereco = { rua: "Av. Brasil", numero: 500 };
const contato = { email: "user@email.com", telefone: "11 99999-0000" };

// Combinando objetos
const perfil = { ...endereco, ...contato };
console.log(perfil);
// { rua: "Av. Brasil", numero: 500, email: "user@email.com", telefone: "11 99999-0000" }

// Atualizando propriedades (a última prevalece em caso de conflito)
const usuarioAtualizado = { ...endereco, numero: 750, complemento: "apto 12" };
console.log(usuarioAtualizado);
// { rua: "Av. Brasil", numero: 750, complemento: "apto 12" }

Esse padrão de atualizar objetos com spread é essencial no React.

Spread em chamadas de função

const numeros = [5, 2, 8, 1, 9, 3];

// Math.max não aceita array — aceita argumentos separados
console.log(Math.max(...numeros)); // 9
console.log(Math.min(...numeros)); // 1

function somar(a, b, c) {
  return a + b + c;
}

const valores = [10, 20, 30];
console.log(somar(...valores)); // 60

Rest Operator — coletando o resto

O mesmo símbolo ... mas com papel oposto: em vez de espalhar, ele coleta múltiplos elementos em um único array ou objeto.

Em funções — quantidade variável de argumentos:

function somar(...numeros) {
  return numeros.reduce((acc, n) => acc + n, 0);
}

console.log(somar(1, 2));           // 3
console.log(somar(1, 2, 3, 4, 5)); // 15
console.log(somar(10, 20));         // 30

numeros é um array com todos os argumentos passados — independente de quantos forem.

Misturando parâmetros normais com rest:

function criarTime(capitao, ...jogadores) {
  console.log(`Capitão: ${capitao}`);
  console.log(`Jogadores: ${jogadores.join(", ")}`);
}

criarTime("Carlos", "Ana", "Pedro", "Lucia", "Marcos");
// Capitão: Carlos
// Jogadores: Ana, Pedro, Lucia, Marcos

O rest deve ser sempre o último parâmetro.

Rest na desestruturação

const { nome, idade, ...resto } = {
  nome: "Rafael",
  idade: 27,
  cidade: "Salvador",
  profissao: "Designer",
  ativo: true,
};

console.log(nome);   // Rafael
console.log(idade);  // 27
console.log(resto);  // { cidade: "Salvador", profissao: "Designer", ativo: true }

Extremamente útil para separar o que você precisa do que você quer repassar adiante.

Com arrays:

const [primeiro, segundo, ...demais] = [10, 20, 30, 40, 50];

console.log(primeiro); // 10
console.log(segundo);  // 20
console.log(demais);   // [30, 40, 50]

Exemplo completo — tudo junto

Veja como esses três recursos se combinam em um cenário real:

const pedidos = [
  { id: 1, cliente: "Ana", total: 150, status: "pago" },
  { id: 2, cliente: "Bruno", total: 320, status: "pendente" },
  { id: 3, cliente: "Clara", total: 80, status: "pago" },
];

// Função que recebe um pedido, extrai o que precisa e retorna um resumo
function resumirPedido({ id, cliente, total, ...extras }) {
  return {
    resumo: `Pedido #${id} — ${cliente}: R$ ${total}`,
    detalhes: { ...extras },
  };
}

pedidos
  .filter(({ status }) => status === "pago")
  .map(resumirPedido)
  .forEach(({ resumo, detalhes }) => {
    console.log(resumo);
    console.log("Detalhes:", detalhes);
  });

// Pedido #1 — Ana: R$ 150
// Detalhes: { status: "pago" }
// Pedido #3 — Clara: R$ 80
// Detalhes: { status: "pago" }

Resumo visual

Recurso Sintaxe O que faz
Desestruturação de objeto const { a, b } = obj Extrai propriedades em variáveis
Desestruturação de array const [x, y] = arr Extrai elementos por posição
Spread em array [...arr1, ...arr2] Espalha elementos
Spread em objeto { ...obj1, ...obj2 } Espalha propriedades
Rest em função function f(...args) Coleta argumentos em array
Rest em desestruturação const { a, ...resto } = obj Coleta o restante

Tarefa para você

const funcionarios = [
  { id: 1, nome: "Joana", cargo: "Dev", salario: 5000, ativo: true },
  { id: 2, nome: "Marcos", cargo: "Design", salario: 4000, ativo: false },
  { id: 3, nome: "Silvia", cargo: "Dev", salario: 5500, ativo: true },
];

// 1. Use desestruturação para extrair nome e salario do primeiro funcionário

// 2. Crie um novo array com todos os funcionários mais um novo:
//    { id: 4, nome: "Lucas", cargo: "QA", salario: 4500, ativo: true }

// 3. Crie uma função que receba um funcionário via desestruturação
//    e retorne: "Joana (Dev) — R$ 5000"

// 4. Separe os dados de identidade (id, nome) do restante usando rest
Ver solução — desestruturação, spread e rest nos quatro pedidos
const funcionarios = [
  { id: 1, nome: "Joana", cargo: "Dev", salario: 5000, ativo: true },
  { id: 2, nome: "Marcos", cargo: "Design", salario: 4000, ativo: false },
  { id: 3, nome: "Silvia", cargo: "Dev", salario: 5500, ativo: true },
];

// ---------------------------------------------------------------
// 1 — nome e salário do primeiro
// ---------------------------------------------------------------
const [{ nome, salario }] = funcionarios; // desestrutura array e objeto de uma vez
console.log(nome, salario); // Joana 5000

// Renomeando na hora, quando o nome da propriedade não serve:
const [{ nome: primeiroNome, salario: primeiroSalario }] = funcionarios;
console.log(primeiroNome, primeiroSalario); // Joana 5000

// Com valor padrão, para propriedade que pode não existir:
const [{ setor = "Não informado" }] = funcionarios;
console.log(setor); // Não informado

// ---------------------------------------------------------------
// 2 — novo array com mais um funcionário
// ---------------------------------------------------------------
const novo = { id: 4, nome: "Lucas", cargo: "QA", salario: 4500, ativo: true };
const equipe = [...funcionarios, novo];

console.log(equipe.length);        // 4
console.log(funcionarios.length);  // 3 — o original não foi tocado

// ---------------------------------------------------------------
// 3 — função que recebe via desestruturação
// ---------------------------------------------------------------
const descrever = ({ nome, cargo, salario }) =>
  `${nome} (${cargo}) — R$ ${salario}`;

console.log(descrever(funcionarios[0])); // Joana (Dev) — R$ 5000

// Formatando o dinheiro de verdade, com separador brasileiro:
const formatar = ({ nome, cargo, salario }) =>
  `${nome} (${cargo}) — ${salario.toLocaleString("pt-BR", {
    style: "currency",
    currency: "BRL",
  })}`;

console.log(formatar(funcionarios[2])); // Silvia (Dev) — R$ 5.500,00

// ---------------------------------------------------------------
// 4 — separar identidade do resto com rest
// ---------------------------------------------------------------
const [primeiro] = funcionarios;
const { id, nome: nomeCompleto, ...dadosProfissionais } = primeiro;

console.log({ id, nomeCompleto });     // { id: 1, nomeCompleto: 'Joana' }
console.log(dadosProfissionais);       // { cargo: 'Dev', salario: 5000, ativo: true }

// É assim que se remove um campo sem `delete`: o que sobra no rest é o
// objeto sem ele. Útil para não devolver senha numa API.
const usuario = { id: 9, email: "a@b.com", senhaHash: "$2y$10$...", ativo: true };
const { senhaHash, ...seguroParaEnviar } = usuario;
console.log(seguroParaEnviar); // { id: 9, email: 'a@b.com', ativo: true }

// ---------------------------------------------------------------
// Rest em parâmetro: quantos argumentos vierem
// ---------------------------------------------------------------
const folhaDe = (...pessoas) => pessoas.reduce((t, p) => t + p.salario, 0);
console.log(folhaDe(...funcionarios)); // 14500

// ---------------------------------------------------------------
// O detalhe que morde: spread copia UM nível
// ---------------------------------------------------------------
const original = { nome: "Joana", contato: { email: "joana@empresa.com" } };
const copia = { ...original };

copia.nome = "Outra";                        // só a cópia muda
copia.contato.email = "invadido@teste.com";  // muda nos DOIS

console.log(original.nome);           // Joana
console.log(original.contato.email);  // invadido@teste.com

// Para cópia profunda de dados simples:
const profunda = structuredClone(original);
profunda.contato.email = "isolado@teste.com";
console.log(original.contato.email);  // invadido@teste.com — agora sim, intacto

Spread e Object.assign fazem cópia rasa: objeto aninhado continua sendo o mesmo objeto nos dois lados. É a origem de bugs em que "editar o formulário mudou a lista". Para copiar de verdade, structuredClone().

Os três pontinhos fazem coisas opostas conforme o lado em que aparecem: à direita espalham, à esquerda recolhem. Essa simetria é a única coisa realmente nova por aqui — o restante é sintaxe que encurta o que já dava para escrever. O ganho aparece onde o código antigo ficava longo o bastante para esconder a intenção: parâmetro de função, cópia de objeto com um campo alterado e troca de valores entre variáveis.

Fontes e Referências

Exercícios

Exercício 1

Anote as três saídas. A terceira surpreende — por quê?

const config = { tema: "escuro", idioma: null };

const { tema, notificacoes = true, idioma = "pt-BR" } = config;

console.log(tema);
console.log(notificacoes);
console.log(idioma);
Ver resposta

✓ Resposta: escuro, true e null. O valor padrão da desestruturação só entra em ação quando a propriedade é undefined — é o caso de notificacoes, que nem existe no objeto. O idioma existe e vale null, e null é um valor como outro qualquer: o padrão não o substitui. É a distinção do artigo sobre tipos primitivos ganhando consequência prática — "não preenchido" e "preenchido com vazio" são coisas diferentes.

Exercício 2

O que cada linha imprime? Conte as vírgulas com cuidado.

const numeros = [10, 20, 30, 40, 50];

const [primeiro, , terceiro, , quinto] = numeros;

console.log(primeiro);
console.log(terceiro);
console.log(quinto);
Ver resposta

✓ Resposta: 10, 30 e 50. Na desestruturação de array, o que vale é a posição, não o nome — e uma vírgula sem nome antes dela ocupa uma posição sem guardar nada. As cinco posições são preenchidas na ordem: primeiro, um vazio, terceiro, outro vazio, quinto. É a diferença essencial em relação à desestruturação de objeto, onde a ordem não importa e o nome é tudo.

Exercício 3

Os dois objetos abaixo têm as mesmas partes, em ordem trocada. Qual é o numero de cada um?

const endereco = { rua: "Av. Brasil", numero: 500 };

const a = { ...endereco, numero: 750 };
const b = { numero: 750, ...endereco };

console.log(a.numero);
console.log(b.numero);
Ver resposta

✓ Resposta: a.numero é 750 e b.numero é 500. No conflito entre propriedades de mesmo nome, a última escrita vence — o objeto é montado da esquerda para a direita e cada chave repetida sobrescreve a anterior. Por isso o padrão de "atualizar um objeto" põe o spread primeiro e as mudanças depois; invertido, o spread desfaz silenciosamente a alteração que você acabou de escrever.

Exercício 4

Este código nem chega a rodar. Que erro o JavaScript aponta, e qual é a regra por trás dele?

function criarTime(...jogadores, capitao) {
  console.log(capitao, jogadores);
}
Ver resposta

✓ Resposta: SyntaxError: Rest parameter must be last formal parameter — e, por ser erro de sintaxe, o arquivo inteiro deixa de rodar, não apenas essa função. A regra tem lógica: o rest coleta tudo o que sobrou, então nada pode vir depois dele; não haveria como decidir quantos argumentos pertencem ao jogadores e qual seria o capitao. A assinatura correta é criarTime(capitao, ...jogadores), com os parâmetros nomeados na frente.

Exercício 5

Uma das duas linhas devolve 9. A outra devolve o quê?

const numeros = [5, 2, 8, 1, 9, 3];

console.log(Math.max(...numeros));
console.log(Math.max(numeros));
Ver resposta

✓ Resposta: A primeira devolve 9; a segunda devolve NaN. O Math.max espera argumentos separados, não uma lista: com o spread, o array vira Math.max(5, 2, 8, 1, 9, 3). Sem ele, o array inteiro chega como um único argumento, o Math.max tenta convertê-lo em número, não consegue, e o resultado é NaN — sem erro nem aviso. É a armadilha típica do NaN: ele se propaga em silêncio por todas as contas seguintes.

Comentários

Mais em Javascript

Formulários: validação e coleta de dados
Formulários: validação e coleta de dados

Um campo de senha e outro de confirmação já mostram o limite do HTML sozinho…

Funções: declaração, expressão e arrow functions
Funções: declaração, expressão e arrow functions

Uma função sem return devolve undefined — e uma arrow com chaves, mas sem…

Tratamento de erros em requisições HTTP
Tratamento de erros em requisições HTTP

Código que só funciona quando tudo dá certo não está pronto para produção…