Clonando objetos de forma segura no Javascript
Tags: javascript, deepclone, es6, web
Published: 2020-03-07 04:52:48

---
title: Clonando objetos de forma segura no Javascript
published: true
draft: true
description: 
tags: Javascript, deepclone, ES6, web
---


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](https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) e o [Spread Operator](https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Operators/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`)..

```js
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`.

```js
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. 
```js
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:

```js
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.

```js
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.

```js
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.

```js
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

```js
objetoClonado.job.name = 'Actor'
```

E ao verificar o valor temos a seguinte saida: 

```js
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ção`JSON.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.

```js
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.
```js
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.

```js
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.

```js
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

![Deep_Clone_JSONParse_JSONStringify](https://i.imgur.com/5V44rDR.png)

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](https://lodash.com/), 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.
```js
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


```js
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](https://lodash.com/docs/2.4.2#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.

```js
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
> - https://alligator.io/js/deep-cloning-javascript-objects/ 
> - https://flaviocopes.com/how-to-clone-javascript-object/
> - https://www.samanthaming.com/tidbits/70-3-ways-to-clone-objects/