In 2026, why does modifying the second level after copying an object also change the original object?
The root cause of modifying the second level of a copied object and having the original object change along with it is that Object.assign and the spread operator only copy the first level: inner objects still point to the same reference. During project integration in 2026, when you encounter the situation where 'modifying a nested field also changes the original object in sync,' it basically falls within this mechanism. Whether to go deeper depends on how many levels you need to modify: if you only change the first level, a shallow copy is enough; if you need to change levels beyond the second, switch to structuredClone or immutable updates.
What distinguishes the first level from the second
Object.assign(target, ...sources) copies every enumerable property from the source object to the target object. Primitive types are passed by value, while objects or arrays are passed by reference. The spread operator {...obj} follows the same rule.
- First-level fields that are primitives such as strings or numbers do not affect the original object after the copy.
- Properties at the second level or deeper copy the reference address. For example, newObj.params.pageSize = 20 directly changes originalObj.params.pageSize.
- Arrays behave the same way: arrayCopy[0].name = 'x' will pass through to the original array.
So judging whether a copy is shallow isn't about counting how many equal signs appear; it's about whether the modification path shares a nested reference. As long as the inner object has not been rebuilt, changes will cross the boundary.
Ask three questions before deciding whether a deep copy is needed
In practical development, memorizing conclusions is not recommended. A more stable approach is to reason through the following order.
- Which level do I need to modify? If only the first level changes, a shallow copy is usually enough. If you need to modify a nested field while keeping the original object unchanged, you must copy deeper.
- Will the original object still be used later? If it is read by multiple modules or acts as a global configuration, shallow-copy deep mutations will pollute other pages; if it's only used for one-off calculations, the risk is much smaller.
- Does the runtime environment allow native APIs? Node.js 17+ and newer browsers support structuredClone. In unsupported environments, detect the capability first, then decide between a recursive clone and a third-party dependency.
Order matters: first determine the modification depth, then decide whether isolation is worth the cost, and finally check environmental constraints. Most 'mutated the original object' cases can be located at the first question.
2026 comparison of shallow and deep copy solutions: cost and limitations
When deep copy is needed, check each solution's cost and applicable boundaries in the list below. The cycles shown are experience ranges for maintenance and integration, not fixed values.
- structuredClone: best for modern browsers and Node.js 17+. It handles Date, Map, Set, and RegExp, and it also detects circular references. Integration cost is about half a day; if older WebView compatibility is needed, capability detection and fallback add roughly 1–2 days.
- JSON.parse(JSON.stringify(obj)): best for plain JSON configuration. Functions and undefined values are dropped, and circular references will throw an error. Daily usage cost is low, but if the data contains Date objects, rework usually takes half a day to one day.
- Recursive cloning: can customize Date, Function, and prototype handling, but it has many edge cases. From implementation to adding tests, the typical range is 1–3 days; every additional data type later may also add maintenance time.
- Immutable libraries (such as Immer): good for global stores with frequent state updates, reducing copying overhead through structural sharing. Introducing the dependency and aligning team practices has a learning and migration range of 3–7 days.
From a 2026 frontend delivery perspective, structuredClone is the preferred option for most projects, but using it still depends on the minimum runtime environment. For projects that must support older Android WebViews, a recursive clone or polyfill is the more dependable path.
Live delivery: reworking a 'crosstalk' filter configuration
In the first half of 2026, an admin dashboard project encountered this kind of crosstalk. Constraints: a three-week delivery cycle, configuration returned by the backend in a single response, and no global state management in the project. The approach: for convenience, a teammate used Object.assign to copy the API config into component data and then pushed new options into the nested options field. Because options still referenced the original object, the mutation polluted the source configuration, causing two other pages that read the same config to show overlapping options.
Fixing this trap took two days. According to the team's retrospective, similar shallow-copy misuse during integration typically costs half a day for the false assumption and 1–3 days for rework. Deeper nesting and more pages sharing the same object increase the cost. A true isolation test is: if no externally visible state is affected after your modification, only then do you own the data.
Applicable and non-applicable boundaries
Shallow copy is suitable for these scenarios: form initialization that only requires replacing default values at the first level; rendering a read-only snapshot that won't modify child structures; or simply assigning an object to another variable name without mutating the original memory. A shallow copy is cheap, and introducing deep copy would make the logic heavier than needed.
Shallow copy is not suitable when: the data has more than two levels and nested nodes need to be added, deleted, or changed later; the object is shared by multiple modules and no one should modify the common data; or the object contains circular references and needs to be serialized. In these cases, persisting with Object.assign accumulates technical debt.
Nor is deep copy a universal remedy. If you clone a large object that updates frequently, each copied frame may incur tens to hundreds of milliseconds of overhead (experience range, varying with device performance). When the data workload is 'frequent small updates,' an immutable library's structural sharing is the better fit.
Frequently asked questions
Why doesn't the original object change when I modify the first level?
Because first-level properties that are strings or numbers are primitive values, copying assigns the value directly, so there are two independent copies in memory. Only when a property's value is an object or an array is the reference copied.
Does the spread operator {...obj} perform a deep copy?
No. Like Object.assign, it copies only the first level, and the second level still holds references. Trying to isolate multi-level modifications with the spread operator is usually ineffective.
Is JSON.parse(JSON.stringify(obj)) safe in these cases?
Not always. It retains only JSON-compatible data; functions and undefined are silently dropped, and circular references cause it to throw. It works for pure configuration data, but be cautious with business data containing Date, Map, and similar types.
Does structuredClone still need a polyfill in 2026?
Modern browsers and Node.js 17+ already include it, but some older WebViews do not. First check typeof structuredClone === 'function', then fall back to a recursive clone if it is unavailable.
Can I directly modify a configuration object returned by the backend after copying it?
First check which level you need to modify. Replacing only the first level can use a shallow copy. To modify nested arrays or objects, it is safer to first destructure the relevant parts and extend your modification path, or use a deep copy.
Ask yourself before opening the code: does the original object need to remain independent? Aligning copy depth with modification intent saves most integration rework. For first-level-only changes, a shallow copy is enough. For second-level-and-beyond changes, switch directly to structuredClone or immutable updates. Perform capability detection once, then decide on the fallback path.
-
It’s 2026. Everyone nodded in requirements review—why are empty states and loading states still being filled in during integration?
Date: Sep 14, 2026 Read: 2
-
CDN refreshed, why are some users still seeing the old page?
Date: Sep 13, 2026 Read: 6
-
When Uni-app ships both a mini program and an app and platform differences keep piling up, where should you start in 2026?
Date: Sep 12, 2026 Read: 12
-
In 2026, is a large Flutter install package the engine's fault or too much bundled in the project?
Date: Sep 11, 2026 Read: 13
-
Does a page URL with a # affect indexing in front-end SEO (Google/Baidu)?
Date: Sep 10, 2026 Read: 21




