Acredito que todos, assim como eu, já precisaram fazer uma cópia de um objeto no Javascript e, felizmente, temos diversas formas de copiar um objeto. Dentre as mais conhecidas temos o Object.assing e o Spread Operator
Object.assign
A primeira forma, e também a mais conhecida, é através do Object.assign que, basicamente, consiste em 2 argumentos. O primeiro é o objeto de destino, ou seja, o novo objeto que irá receber os valores do objeto original, o segundo é o objeto que você deseja copiar (que vamos chamar de objetoOrigem
)..
var objetoOrigem = { name: 'Chandler Bing', animal: 'dog' }
var objetoClonado = Object.assign({}, objetoOrigem)
O método .assign
retorna um novo objeto, sendo assim podemos alterar as propriedades do novo objeto sem alterar o objetoOrigem
.
objetClonado.name = 'Joey Tribbiani'
console.log(objetoOrigem)
// { name: 'Chandler Bing', animal: 'dog' }
console.log(objetoClonado)
// { name: 'Joey Tribbiani', animal: 'dog' }
Spread Operator
A segunda forma é conhecida como Spread Operator, que consiste em expandir as propriedades dentro do objeto (como string, números e array) para 1 ou n propriedades, em outras palavras, consiste em expandir um objeto maior em várias propriedades daquele objeto, no exemplo fica mais claro.
var array = [1,2,3]
var fn = function(a,b,c) {
console.log(a,b,c)
// 1 2 3
}
fn(...array)
Com o Spread Operator, eu posso quebrar o objeto original em n propriedades. Seguindo essa lógica poderiamos recuperar os atributos do objetoOrigem
e construir um novo objeto chamado objetoClonado
, veja no exemlo a seguir:
var objetoOrigem = { name: 'Chandler Bing' }
var objetoClonado = { ...objetoOrigem }
Beleza, então quando tentamos alterar o nome do novo objeto clonado, o objeto original ainda manteria com os mesmos valores.
objetoOrigem.name = 'Joey Tribbiani'
console.log(objetoOrigem)
// { name: 'Chandler Bing' }
console.log(objetoClonado)
// { name: 'Joey Tribbiani' }
No fim das contas o Spread Operator acaba se tornando um substituto para o Object.assing
Shallow clone
Tanto Objetct.assign
quanto Spread, fazem um clone que chamamos de Shallow clone. Shallow clone copia apenas valores enumerados como String, Number e Array. Quando clonamos um objeto que possui uma chave cujo valor é outro objeto, o que o Shallow clone faz é copiar a referencia de memória para o novo objeto clonado, sendo assim, os dois objetos compartilham a mesma referência.
Deep clone
Deep clone se baseia em criar um novo objeto a partir do objeto original, criando uma nova referência de memória para os nested objects, caso exista. Existem algumas formas de fazer.
Shallow Clone vs Deep Clone
As duas formas funcionam muito bem, mas só quando estamos lidando com objetos simples, ou seja, objetos compostos de valores primitivos, quando começamos a lidar com objetos mais complexos onde temos nested objects ou funcões, qualquer uma das abordagens listada a cima se tornam inviáveis, o por que ? bem podemos ver em um exemplo real, vamos considerar o seguinte objeto.
var objetoOrigem = {
name: 'Chandler Bing',
age: 25,
job: {
name: 'Unknown'
}
}
Ao clonarmos o objeto e modificar o nome do objeto clonado obteremos o seguinte resultado.
var objetoClonado = { ...objetoOrigem }
objetoClonado.name = 'Joey Tribbianiy'
console.log(objetoOrigem)
// { name: 'Chandler Bing', age: 25, job: { name: 'Unknown' } }
console.log(objetoClonado)
// { name: 'Joey Tribbiani', age: 25, job: { name: 'Unknown' }
Modificamos o objetoClonado
sem alterar o objetoOrigem
. Perfeito!
Agora vamos tentar modificar a propriedade job
do objeto clonado
objetoClonado.job.name = 'Actor'
E ao verificar o valor temos a seguinte saida:
console.log(objetoOrigem)
// { name: 'Chandler', age: 25, job: { name: 'Actor' } }
console.log(objetoClonado)
// { name: 'Joe', age: 25, job: { name: 'Actor' } }
Ao alterar a propriedade objetoClonado.job
alterou tanto para objetoClonado
quanto para o objetoOrigem
.
Deep Clone utilizando JSON.stringify
e JSON.parse
Em alguns lugares você pode ter visto um deep clone usando o a implementaçãoJSON.stringify
e JSON.parse
. que consiste em transformar o seu objeto origem em JSON, e em seguida utilizando o JSON.parse
para criar um novo objeto, como mostra o código a baixo.
var objetoOrigem = {
name: 'Chandler',
age: 25,
job: {
name: 'Unknown'
},
myNameAndJob() {
return `My name is ${this.name} and I work as ${this.job.name}`
}
}
Dessa vez criamos um objeto que possui uma função que retorna o name
e o job
em uma única string, agora vamos clonar o objeto.
var objetoClonado = JSON.parse(JSON.stringify(objetoOrigem))
E ao tentar modificar as propriedades do objeto clonado e executar a função myNameAndJob
, gera a seguinte saída.
objetoClonado.name = 'Joe'
objetoClonado.job.name = 'Actor'
console.log(objetoOrigem.myNameAndJob())
// My name is Chandler and I work as Unknown
console.log(objetoClonado.myNameAndJob())
// console.log(objetoClonado.myNameAndJob())
// TypeError: objetoClonado.myNameAndJob is not a function
O Erro foi gerado porque, ao utilizar o JSON.stringify
no objeto criado, o resultado foi uma string da estrutura de dados do objeto original, ou seja, não existe funções no novo objeto, foi copiado apenas os atributos e os nested objects.
Isso também se torna um problema quando o seu objeto possui propriedades do tipo Date
, por exemplo.
var objetoComDate = {
name: 'Chandler',
birthday: new Date('1994-01-01T00:00:00')
}
var objetoClonado = JSON.parse(JSON.stringify(objetoComDate))
Ao exibir os dois objetos, note a diferença
O Objeto objetoComDate
possui a propriedade birthday
como tipo Date
, enquanto o objetoClonado
transformou a propriedade Date
em uma String
contendo o valor da data.
Lodash
A forma mais indicada é usando funcionalidades de bibliotecas maduras, testadas e mantida pela comunidade como o Lodash, Lodash é uma biblioteca em Javascript que contém métodos utilitários para trabalhar com Arrays
, Objects
, String
e Numbers
.
Podemos instalar o lodash com o comando npm install lodash --save
, o legal do Lodash é que podemos importar somente os métodos que vamos usar, assim não carregamos toda a biblioteca de forma necessária.
No Lodash temos um método que faz um deepClone de um objeto, podemos importar o cloneDeep
de duas formas
A primeira forma é importar toda a biblioteca e usar o método desejado, como no exemplo abaixo.
var _ = require('lodash')
var objetoOrigem = {
name: 'Chandler',
age: 25,
job: {
name: 'Unknown'
},
myNameAndJob() {
return `My name is ${this.name} and I work as ${this.job.name}`
}
}
const objetoClonado = _.cloneDeep(objetoOrigem, {}, true)
A segunda forma é importando somente o método desejado da biblioteca
var _cloneDeep = require('lodash/cloneDeep')
var objetoOrigem = {
name: 'Chandler',
age: 25,
job: {
name: 'Unknown'
},
myNameAndJob() {
return `My name is ${this.name} and I work as ${this.job.name}`
}
}
const objetoClonado = _cloneDeep(objetoOrigem, {}, true)
Para qualquer uma das formas, o resultado final será o mesmo, ja que com o cloneDeep é possível clonar o objeto e seus nested objects de forma que o objeto clonado não possua nenhuma referencia compartilhada com o objetoOrigem
, como no código abaixo.
var _cloneDeep = require('lodash/cloneDeep')
var objetoOrigem = {
name: 'Chandler',
age: 25,
job: {
name: 'Unknown'
},
myNameAndJob() {
return `My name is ${this.name} and I work as ${this.job.name}`
}
}
const objetoClonado = _cloneDeep(objetoOrigem, {}, true)
objetoClonado.name = 'Joe'
objetoClonado.job.name = 'Actor'
console.log(objetoOrigem.myNameAndJob())
// My name is Chandler and I work as Unknown
console.log(objetoClonadoComClone.myNameAndJob())
// My name is Joe and I work as Actor
Referencias
Top comments (6)
Bom trabalho,Guilherme! Aguardando os próximos!
Valeeeu, agradeço de coração pela força e incentivo hahaha.
Acho incriável mesmo programando com JS a um bom tempo, sempre estou descobrindo algo novo sobre a linguagem!
Como se clona instâncias de classes?
lodash transforma em plain object ?
Hoje em dia eu uso um design pattern Builder pra isso. Sabes de melhor alternativa?
Parabéns pelo post!
Opa Afif, já agradeço pela força e por ter lido o artigo haha.
Sobre a sua dúvida bom vamos lá, cara na minha opinião, se for apenas para fazer um clone, o ideal seria usar o
cloneDeep
do lodashSe tiver alguma dúvida e eu puder ajudar, é só me chamar haha
Ótimo tema para trazer para discussão. Valeu