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

APIs públicas: exemplos práticos
APIs públicas: exemplos práticos

Seis APIs públicas, nenhuma exigindo cadastro ou chave: ViaCEP para endereço…

Criando e Removendo Elementos Dinamicamente
Criando e Removendo Elementos Dinamicamente

Boa parte de uma interface não está no HTML quando a página carrega — quem a…

Trabalhando com JSON
Trabalhando com JSON

JSON parece um objeto JavaScript, mas é bem mais pobre: não tem data, não tem…