The spread operator is a shallow copy operation. That means that while the arrays are separate objects, they both refer to the same element in every index. Let's provide a very simple example that illustrates this:
let a = [{ value: 'hello world' }]
let b = a;
b[0].value = 'goodbye world';
console.log(a); // [{ value: 'goodbye world' }]
console.log(b); // [{ value: 'goodbye world' }]
console.log(a===b); // true
console.log(a[0] === b[0]); // true
The ===
is strict equality, for objects, meaning "are two variables pointing to the same physical object in memory."
Converting this to just the spread operator, we get:
let a = [{ value: 'hello world' }]
let b = [...a];
b[0].value = 'goodbye world';
console.log(a); // [{ value: 'goodbye world' }]
console.log(b); // [{ value: 'goodbye world' }]
console.log(a===b); // false
console.log(a[0] === b[0]); // true
As you can see, the spread operator copied the array, but not the individual elements.
You can fix this by copying the elements using Array.prototype.map and the spread operator together:
let a = [{ value: 'hello world' }]
let b = a.map((v)=>({...v}));
b[0].value = 'goodbye world';
console.log(a); // [{ value: 'hello world' }]
console.log(b); // [{ value: 'goodbye world' }]
console.log(a===b); // false
console.log(a[0] === b[0]); //false
This strategy is useful for most situations, can perform better than JSON.stringify, and doesn't run in to the "circular reference" problem that you can get when dealing with recursive structures.
Do not use JSON round-trips if you want decent performance. Instead, use structuredClone, which is already available in 75% of browsers in use on the web, and well as a polyfill to fill the gap with older and otherwise unsupported browsers.