How to Clone an Object in JavaScript
Tags: javascript
Published: 2023-11-10 22:39:30

`How to... js series`

The way JavaScript handles assignments to copy objects is different from how it handles primitive values. Instead of holding values, it uses a pointer to the value in memory.
This concept is known as `assignment by reference`, where the variable doesn't store the actual value but a reference to the object's memory location. This implies that if two variables point to the same object, any modification to one of them will affect both. 

### Trying direct assignment

`code`
```javascript
const weather= { today:'🌞'}
const currentWeather = weather
currentWeather.today = '🌧'
```
`test`
```javascript
test('should preserve the value', () => {
 expect(weather.today).toBe('🌞')
})
```
❌ `FAIL` should preserve the value

It fails because an object is not a primitive value so in this case JavaScript uses assignment by reference.

# Strategies

Depending on the original object and specific needs, one can choose between two copying strategies in JavaScript:


## Shallow copy

A shallow copy creates a new object where only the top-level structure of the object is duplicated, while the nested objects or elements inside the original object still maintain their references.

### Using spread syntax

`code`
```javascript
const weather= {
  today:'🌞',
  forecast: { morning:'🌞'}
}
const currentWeather = { ...weather }
currentWeather.today = '🌧'
currentWeather.forecast.morning = '⛅'
```
`test`
```javascript
test('should preserve the value', () => {
 expect(weather.today).toBe('🌞')
})

test('should preserve the nested value', () => {
 expect(weather.forecast.morning).toBe('🌞')
})
```

✅ `PASS` should preserve the value
❌ `FAIL` should preserve the nested value

### Using Object.assign()

`code`
```javascript
const weather= {
  today:'🌞',
  forecast: { morning:'🌞'}
}
const currentWeather = Object.assign({}, weather)
currentWeather.today = '🌧'
currentWeather.forecast.morning = '⛅'
```
`test`
```javascript
test('should preserve the value', () => {
 expect(weather.today).toBe('🌞')
})

test('should preserve the nested value', () => {
 expect(weather.forecast.morning).toBe('🌞')
})
```

✅ `PASS` should preserve the value
❌ `FAIL` should preserve the nested value

## Deep copy

In contrast, a deep copy creates independent copies of all nested objects, ensuring that there are no shared references.

### Cloning Objects using JSON.parse()/JSON.stringify()

`code`
```javascript
const weather= {
  today:'🌞',
  forecast: { morning:'🌞'}
}
const currentWeather = JSON.parse(JSON.stringify(weather))
currentWeather.today = '🌧'
currentWeather.forecast.morning = '⛅'
```
`test`
```javascript
test('should preserve the value', () => {
 expect(weather.today).toBe('🌞')
})

test('should preserve the nested value', () => {
 expect(weather.forecast.morning).toBe('🌞')
})
```

✅ `PASS` should preserve the value
✅ `PASS` should preserve the nested value

⚠️ **NOTE:** JSON.parse/JSON.stringify method has important limitations:

- _Date_ is converted to string
- _Infinity_ and _NaN_ are converted to null
- _undefined_, _function_ and _Symbol_ as value in object property are ignored and convert to null in arrays.

### Using structuredClone() ❤️

`code`
```javascript
const weather= {
  today:'🌞',
  forecast: { morning:'🌞'}
}
const currentWeather = structuredClone(weather)
currentWeather.today = '🌧'
currentWeather.forecast.morning = '⛅'
```
`test`
```javascript
test('should preserve the value', () => {
 expect(weather.today).toBe('🌞')
})

test('should preserve the nested value', () => {
 expect(weather.forecast.morning).toBe('🌞')
})
```

✅ `PASS` should preserve the value
✅ `PASS` should preserve the nested value

Structured cloning offers distinct advantages over JSON.parse()/JSON.stringify(). It excels in managing intricate objects beyond the capabilities of JSON, including objects with binary data or cyclic object graphs.

Nonetheless, structured cloning does come with certain limitations. It is unable to handle prototypes, functions, Symbol, and certain values like Error and DOM nodes. [ref](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#things_that_dont_work_with_structured_clone)

To fully values support in deep copy (functions, Symbol..) is necessary iteration strategy, but in most cases structuredClone() is good enough.

It's important to note that the structuredClone() method is not supported in every browser.

Support for structuredClone:
* [caniuse](https://caniuse.com/mdn-api_structuredclone)
* [browserslist](https://browsersl.ist/)