Today, while investigating a bug in our app, I witnessed a very surprisingly behavior in JavaScript’s structuredClone
.
This method promises to create a deep clone of a given value. This was previously achieved using the JSON.parse(JSON.stringify(value))
technique, and on paper structuredClone
appears to be a superset per say of this technique, yielding the same result, while also supporting things like Dates and circular references.
However, today I learned that if you use structuredClone
to clone an object containing reference type variables pointing to the same reference, these references will be kept, as opposed to creating new values with different references.
Here is a toy example to demonstrate this behavior:
const someSharedArray = ['foo', 'bar']
const myObj = {
field1: someSharedArray,
field2: someSharedArray,
field3: someSharedArray,
}
const myObjCloned = structuredClone(myObj)
console.log(myObjCloned)
/**
{
"field1": ["foo", "bar"],
"field2": ["foo", "bar"],
"field3": ["foo", "bar"],
}
**/
myObjCloned.field2[1] = 'baz'
// At this point:
// Expected: only `field2`'s value should change, because `myObjCloned` was deeply cloned.
// Actual: all fields' values change, because they all still point to `someSharedArray`
console.log(myObjCloned)
/**
{
"field1": ["foo", "baz"],
"field2": ["foo", "baz"],
"field3": ["foo", "baz"],
}
**/
This is a very surprising behavior of structuredClone
, because:
- It claims to perform deep cloning
- It doesn’t happen when using
JSON.parse(JSON.stringify(value))
In our codebase, we always use structuredClone
, but in light of this new finding, I’m considering changing all structuredClone
usages to JSON.parse(JSON.stringify(value))
, in order to prevent unexpected results.