Clonando objetos em JavaScript - Shallow vs Deep
Compreenda a diferença entre shallow copy e deep copy em JavaScript. Saiba quando usar cada abordagem e explore suas vantagens.

Clonar objetos é uma tarefa comum para desenvolvedores JavaScript. A clonagem nos permite criar versões independentes e isoladas, preservando a integridade dos dados e evitando efeitos colaterais indesejados. Essa prática é especialmente útil em situações em que desejamos modificar apenas uma cópia do objeto, enquanto mantemos o objeto original intacto.
const obj = {
protocol: 'https',
host: 'consolelog.com.br',
path: '/'
};
const clone = {
...obj
};
clone.path = '/tag/dicas-artigos-exemplos-javascript/';
console.log(obj.path);
// "/"
console.log(clone.path);
// "/tag/dicas-artigos-exemplos-javascript/"
Além disso, em casos de gerenciamento de estado, como no Redux ou NGRX, é comum lidar com objetos imutáveis. Nestes casos, clonar o objeto se torna necessário para realizar modificações específicas, entregando um novo objeto como resultado do processo.
export const scoreboardReducer = createReducer(
initialState,
on(ScoreboardPageActions.homeScore, (state) => ({
...state,
home: state.home + 1,
}))
);
// https://ngrx.io/guide/store/reducers
Estudando os métodos de clonagem de objetos em JavaScript podemos evitar problemas como no exemplo abaixo:
const obj = {
startDate: new Date("2023-01-01T12:00:00.000Z")
};
const clone = Object.assign(obj, {});
// Somando 5 minutos no clone.startDate
const fiveMinInMS = 5 * 60 * 1e3;
clone.startDate.setTime(
clone.startDate.getTime() + fiveMinInMS
);
// "2023-01-01T12:05:00.000Z"
console.log(clone.startDate.toISOString());
// O obj.startDate === clone.startDate
// "2023-01-01T12:05:00.000Z"
console.log(obj.startDate.toISOString());
Há diversas abordagens para clonar objetos em JavaScript, tais como usar Object.assign()
(exemplo acima) ou o spread operator (...
) para efetuar uma shallow copy (cópia "superficial"). Também podemos recorrer a função nativa structuredClone()
ou bibliotecas como lodash.cloneDeep()
para efetuar uma deep copy (cópia "profunda"). A escolha da abordagem depende das necessidades específicas de cada cenário e é isso que vamos explorar neste texto.
Shallow Copy
Quando utilizamos o ...
(spread operator) ou o Object.assign()
, estamos efetuando uma shallow copy de um objeto. Isso significa que estamos criando um novo objeto com uma nova referência. Mas é importante destacar alguns detalhes:
- as propriedades do tipo primitivo (primitive type), como por exemplo
string
enumber
, são copiadas gerando um novo valor, ocupando um novo espaço de memória, ou seja, são cópias independentes. - As propriedades do tipo referência (reference type), como arrays e objetos, tem suas referências copiadas, ou seja, não é feita uma cópia do objeto em si, mas sim do endereço de memória onde o objeto está armazenado. Portanto, as propriedades reference type do clone apontarão para o mesmo endereço das propriedades do objeto original. Podemos visualizar esse comportamento no exemplo a seguir:
const obj = {
nome: 'consolelog.com.br',
tags: [1, 2, 3],
objs: {
item1: 'valor1',
item2: 'valor2'
}
};
// Utilizando o spread operator (...)
// para clonar o objeto
const clone = {...obj};
// ou poderíamos utilizar o Object.assign:
// const clone = Object.assign({}, obj);
console.log(
typeof obj, // object (reference type)
typeof obj.nome, // string (primitive type)
typeof obj.tags, // object (reference type)
typeof obj.objs, // object (reference type)
);
console.log(
obj === clone, // false
obj.nome === clone.nome, // true
obj.tags === clone.tags, // true
obj.objs === clone.objs, // true
);
Analisando o código acima:
- a variável
obj
tem um campo primitivo (nome
) e dois referenciados (tags
eobjs
) clone
foi gerado a partir doobj
utilizando o spread operator, portanto podemos afirmar queobj !== clone
- O campos
tags
eobjs
das váriaveisobj
eclone
apontam para o mesmo objeto. Isso é um aspecto crucial a ser destacado, pois as cópias foram feitas por referência. Portanto, se fizermos uma alteração emobj.tags
, o resultado será refletido emclone.tags
, uma vez que ambos apontam para o mesmo local na memória. Por essa razão, se adicionarmos um novo item utilizandoclone.tags.push(4)
, o mesmo valor será adicionado aobj.tags
.
clone.tags.push(4);
// [1, 2, 3, 4]
console.log(clone.tags);
// [1, 2, 3, 4] (4)
console.log(obj.tags);
O mesmo exemplo acima se aplica a obj.objs
ou clone.objs
.
Um outro cenário comum é quando o objeto clonado tem algum campo do tipo Date
, veja no exemplo abaixo:
const obj = {
data: new Date("2023-08-01T12:00:00.000-0300")
};
const clone = Object.assign({}, obj);
// Adiciona 10 minutos
const dezMinutos = 10 * 60 * 1e3;
clone.data.setTime(clone.data.getTime() + dezMinutos);
console.log(clone.data);
console.log(obj.data);
// Output:
Tue Aug 01 2023 12:10:00 GMT-0300 (Brasilia Standard Time) (7.16.0.min.js, line 2)
Tue Aug 01 2023 12:10:00 GMT-0300 (Brasilia Standard Time) (7.16.0.min.js, line 2)
Deep Copy
Ao contrário da shallow copy, uma deep copy cria uma nova instância do objeto original e também cria novas instâncias de todos os objetos referenciados. Isso significa que todas as propriedades e membros do objeto original são copiados, não apenas as referências. Como resultado, as alterações feitas em um objeto não afetam o outro.
Uma das formas de realizar uma deep copy é através do uso do structuredClone
, que é razoavelmente recente, mas suportado pelos navegadores atuais e também a partir da versão 17 do Node.js. Veja o exemplo abaixo:
const obj = {
nome: "consolelog.com.br",
tags: [1, 2, 3],
objs: {
item1: "valor1",
item2: "valor2",
},
data: new Date("2023-12-31T23:59:59.000-0300"),
};
const clone = structuredClone(obj);
console.log(
typeof obj, // object
typeof obj.nome, // string
typeof obj.tags, // object
typeof obj.objs // object
);
// Alterando o valor do nome, que é do
// tipo primitivo:
clone.nome = "https://consolelog.com.br";
console.log(
obj === clone, // false
obj.nome === clone.nome, // false
obj.tags === clone.tags, // false
obj.objs === clone.objs, // false
obj.data === clone.data // false
);
// Problema:
console.log(
obj.data, // Sun Dec 31 2023 23:59:59 GMT-0300 (Brasilia Standard Time)
obj.data instanceof Date, // true
clone.data, // Sun Dec 31 2023 23:59:59 GMT-0300 (Brasilia Standard Time)
clone.data instanceof Date // true
);
Se o objeto original possuir alguma função que não é serializável, você tomará o seguinte erro: DataCloneError
DataCloneError
DOMException
Thrown if any part of the input value is not serializable.
https://developer.mozilla.org/en-US/docs/Web/API/structuredClone#exceptions
Um outro exemplo envolvendo datas:
const obj = {
data: new Date("2023-08-01T12:00:00.000-0300")
};
// Trecho comentado e utilizado no
// exemplo shallow copy
// const clone = Object.assign({}, obj);
const clone = structuredClone(obj);
// Adiciona 10 minutos
const dezMinutos = 10 * 60 * 1e3;
clone.data.setTime(clone.data.getTime() + dezMinutos);
console.log(clone.data);
console.log(obj.data);
Embora tenhamos o recurso structuredClone
nativo, pessoalmente, desencorajo o uso de bibliotecas ou mesmo da combinação JSON.stringify + JSON.parse
. No entanto, para fins de estudo, vamos examinar o método JSON.parse(JSON.stringify(obj))
:
const obj = {
nome: "consolelog.com.br",
tags: [1, 2, 3],
objs: {
item1: "valor1",
item2: "valor2",
},
data: new Date("2023-12-31T23:59:59.000-0300"),
funcao: () => console.log('ola'),
};
const clone = JSON.parse(JSON.stringify(obj));
console.log(
typeof obj, // object
typeof obj.nome, // string
typeof obj.tags, // object
typeof obj.objs // object
);
// Alterando o valor do nome, que é do
// tipo primitivo:
clone.nome = "https://consolelog.com.br";
console.log(
obj === clone, // false
obj.nome === clone.nome, // false
obj.tags === clone.tags, // false
obj.objs === clone.objs, // false
obj.data === clone.data, // false
obj.funcao === clone.funcao // false
);
// Problema:
console.log(
obj.data, // Sun Dec 31 2023 23:59:59 GMT-0300 (Brasilia Standard Time)
obj.data instanceof Date, // true
clone.data, // 2024-01-01T02:59:59.000Z
clone.data instanceof Date, // false
typeof obj.funcao, // function
typeof clone.funcao // undefined
);
Podemos destacar alguns pontos do código acima:
- tivemos problemas na cópia do objeto
Date
. Quando executamos oJSON.stringify
, o objetoDate
é convertido para o formato ISO8601, ou seja, umastring
. Então quando efetuarmos oparse
a data continuará comostring
e não um objetoDate
. - Não funcionará com
Map
eSet
Considerações
Ao clonar objetos em JavaScript, é importante entender a diferença entre deep clone e shallow clone. O deep clone cria uma cópia completa e independente do objeto, incluindo todos os seus níveis de aninhamento. Por outro lado, o shallow clone cria uma cópia superficial, onde as propriedades de níveis mais profundos são compartilhadas entre o objeto original e o clone.
Existe uma diferença entre performance destes dois métodos. Fiz uma pequena simulação apenas para dar uma ideia. Veja abaixo:
const obj = {
"product": "Live JSON generator",
"version": 3.1,
"releaseDate": "2014-06-25T00:00:00.000Z",
"demo": true,
"person": {
"id": 12345,
"name": "John Doe",
"phones": {
"home": "800-123-4567",
"mobile": "877-123-1234"
},
"email": [
"[email protected]",
"[email protected]"
],
"dateOfBirth": "1980-01-02T00:00:00.000Z",
"registered": true,
"emergencyContacts": [{
"name": "Jane Doe",
"phone": "888-555-1212",
"relationship": "spouse"
},
{
"name": "Justin Doe",
"phone": "877-123-1212",
"relationship": "parent"
}
]
}
}
console.time('shallow copy');
const shallowClone = {
...obj
};
console.timeEnd('shallow copy');
console.time('deep copy');
const deepCopy = structuredClone(obj);
console.timeEnd('deep copy');
// Output:
// shallow copy: 0.004ms
// deep copy: 0.027ms
É possível observar que existe uma diferença significativa na performance. Então ao lidar com objetos grandes ou complexos, também é essencial considerar o impacto na performance.
Em resumo, a escolha entre deep e shallow copy depende das necessidades de cada caso.
Links:
- https://medium.com/version-1/cloning-an-object-in-javascript-shallow-copy-vs-deep-copy-fa8acd6681e9#:~:text=There are two ways to,of the object are copied
- https://www.javascripttutorial.net/object/3-ways-to-copy-objects-in-javascript/
- https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
- https://github.com/nodejs/node/issues/34355