Spread e Rest em JavaScript: arrays, objetos e os gotchas de cópia rasa
Spread operator e rest em JavaScript: como funcionam em arrays, objetos e funções, e os gotchas de cópia rasa que todo dev encontra.
Jonathan Juliani
Engenheiro de Software Sr - Full Stack · WithClutch Inc. | Juliani Tecnologia
JavaScript
Você faz const copia = [...original], modifica a cópia, e o array original muda junto. Ou clona um objeto com spread e descobre que uma propriedade aninhada ainda é referência compartilhada. O spread parece mais simples do que é — e os erros que ele esconde são os mais difíceis de debugar porque não jogam exceção nenhuma.
Spread e rest usam a mesma sintaxe (...) com propósitos opostos: spread expande um iterável nos seus elementos, rest coleta elementos separados num array. Entender quando cada um entra em ação — e onde a cópia rasa te pega — é o que este post cobre.
O que você vai aprender:
Spread em arrays: clonar, combinar e espalhar como argumento
Spread em objetos: substituindo Object.assign de vez
Rest parameters: coletando argumentos variádicos com elegância
Os gotchas de cópia rasa e como fazer deep copy quando precisar
Pré-requisitos: arrays, objetos e funções básicas em JavaScript. Se quiser ver spread em objetos no contexto das features do ES2018 — onde ele foi introduzido — o post ECMAScript ES7 ao ES10 tem a visão geral de onde cada feature veio.
Spread em arrays: clonar, combinar e espalhar argumentos
A sintaxe ... antes de um array o expande nos seus elementos individuais. O caso mais comum é criar uma cópia superficial:
código
Inscreva-se no Ledger da Engenharia
Receba notas de arquitetura e performance na sua caixa de entrada. Mesma lista do convite—inscreva-se aqui quando quiser.
Sobrescrever propriedades específicas sem mutar o original — padrão muito comum em Redux e gerenciamento de estado imutável:
código
function atualizarUsuario(usuario, alteracoes) { return { ...usuario, ...alteracoes }; // retorna novo objeto, não muta o original}const novo = atualizarUsuario(usuario, { idade: 29 });
"Tá Jon, e qual a diferença pro Object.assign mesmo?"
Object.assign muta o primeiro argumento:
código
const resultado = Object.assign(padrao, preferencias);// padrao foi mutado — resultado e padrao são o mesmo objeto
Com spread, você sempre recebe um objeto novo. A mutação acidental de Object.assign é a razão pela qual o spread em objetos virou padrão. Se quiser usar Object.assign sem mutar, você passa um objeto vazio como primeiro argumento: Object.assign({}, a, b) — mas nesse caso o spread é mais legível.
Rest parameters: o spread ao contrário
Rest parameters coletam os argumentos restantes de uma chamada de função num único array. É o ... no lado receptor, não no chamador:
código
function somar(...numeros) { return numeros.reduce((total, n) => total + n, 0);}somar(1, 2, 3); // 6somar(10, 20, 30, 40); // 100
Você pode combinar parâmetros normais com rest — mas rest tem que ser o último:
código
function log(nivel, ...mensagens) { // nivel é o primeiro argumento // mensagens é um array com o resto mensagens.forEach(msg => console.log(`[${nivel}]`, msg));}log('INFO', 'servidor iniciado', 'porta 3000');// [INFO] servidor iniciado// [INFO] porta 3000
A diferença entre rest e o objeto arguments:
código
function comArguments() { console.log(arguments); // object Arguments — não é array de verdade console.log([...arguments]); // array, mas gambiarra}function comRest(...args) { console.log(args); // array de verdade — tem .map, .filter, tudo}
arguments não existe em arrow functions, não é um array real e não funciona com desestruturação. Rest parameters resolve todos esses problemas.
"Tá Jon, quando uso rest e quando uso spread?"
Fácil de lembrar: rest está na definição da função (coletando o que chega), spread está na chamada (expandindo o que você manda). São operações inversas:
Gotchas: cópia rasa, objetos aninhados e spread em strings
Cópia rasa (shallow copy)
Spread copia apenas o primeiro nível. Se o array ou objeto contém referências — outros objetos, arrays aninhados — essas referências são compartilhadas, não clonadas:
[IMAGEM: diagrama mostrando dois objetos com spread — propriedades primitivas são copiadas por valor, mas propriedades objeto apontam para a mesma referência na memória | alt: "Diagrama de cópia rasa com spread em JavaScript: valores primitivos copiados, objetos aninhados compartilham referência"]
[IMAGEM: screenshot do console do navegador mostrando original.endereco.cidade como 'RJ' após mutação via copia | alt: "Console do navegador mostrando que o spread de objeto aninhado compartilha referência — original é mutado ao modificar a cópia"]
O mesmo acontece com arrays de objetos:
código
const usuarios = [{ nome: 'Ana' }, { nome: 'Bia' }];const copia = [...usuarios];copia[0].nome = 'Carlos';console.log(usuarios[0].nome); // 'Carlos' — o objeto dentro ainda é referência compartilhada
"Tá Jon, mas então o spread é inútil pra cópia de verdade?"
Não — spread é perfeito pra estruturas rasas, que é a maioria dos casos. O problema é usar spread esperando deep copy quando a estrutura tem aninhamento.
Quando você precisa de uma cópia profunda, tem duas opções principais:
structuredClone (moderno, Node.js 17+ e browsers modernos) — é a forma correta:
código
const copia = structuredClone(original); // clone profundo, sem gambiarra
JSON.parse(JSON.stringify(...)) (funciona em mais ambientes, mas tem limitações):
código
const copia = JSON.parse(JSON.stringify(original));// perda de: undefined, functions, Date vira string, Set/Map viram {}
Use structuredClone se o ambiente suportar. Só caia no JSON.parse/stringify se precisar de compatibilidade ampla e souber as limitações.
Spread em strings
Uma curiosidade útil: spread funciona em qualquer iterável, incluindo strings:
[PREENCHER: adicionar CTA personalizado aqui — pode ser algo como "Qual gotcha de spread te pegou em produção? Aquele de cópia rasa pega todo mundo pelo menos uma vez. Me fala nos comentários — e se viu alguma coisa errada aqui, corrige sem cerimônia :D"]