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
- MDN Web Docs — Desestruturação: https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
- MDN Web Docs — Spread Syntax: https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Operators/Spread_syntax
- MDN Web Docs — Rest Parameters: https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Functions/rest_parameters
- JavaScript.info — Destructuring: https://javascript.info/destructuring-assignment
- JavaScript.info — Spread and Rest: https://javascript.info/rest-parameters-spread
- Airbnb JavaScript Style Guide — Destructuring: https://github.com/airbnb/javascript#destructuring
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.