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
const weather= { today:'🌞'}
const currentWeather = weather
currentWeather.today = '🌧'
test
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
const weather= {
today:'🌞',
forecast: { morning:'🌞'}
}
const currentWeather = { ...weather }
currentWeather.today = '🌧'
currentWeather.forecast.morning = '⛅'
test
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
const weather= {
today:'🌞',
forecast: { morning:'🌞'}
}
const currentWeather = Object.assign({}, weather)
currentWeather.today = '🌧'
currentWeather.forecast.morning = '⛅'
test
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
const weather= {
today:'🌞',
forecast: { morning:'🌞'}
}
const currentWeather = JSON.parse(JSON.stringify(weather))
currentWeather.today = '🌧'
currentWeather.forecast.morning = '⛅'
test
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
const weather= {
today:'🌞',
forecast: { morning:'🌞'}
}
const currentWeather = structuredClone(weather)
currentWeather.today = '🌧'
currentWeather.forecast.morning = '⛅'
test
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
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:
Top comments (0)