Destructuring extracts values from objects or arrays. Spread syntax creates a new outer object or array. Neither of those automatically deep-copies nested data. If you need to copy nested structures, evaluate structuredClone first, and do not treat JSON.parse(JSON.stringify(value)) as a universal deep clone.
Object destructuring
const user = {
id: 42,
profile: { name: "Steven" },
role: "editor",
}
const { id, role } = user
Rename properties and provide defaults:
const { role: userRole, locale = "en-US" } = user
Defaults apply only when the value is undefined. They do not replace null.
Nested destructuring works, but it can throw when the parent is missing:
const {
profile: { name },
} = user
If profile may be absent, validate the data or provide a safe default:
const { profile: { name } = {} } = user
In TypeScript, be careful with empty-object defaults. Do not hide real type uncertainty with unsafe assertions.
Array destructuring
const [first, second] = ["Mac", "Windows", "Linux"]
const [head, ...rest] = [1, 2, 3, 4]
Swapping variables:
let left = "L"
let right = "R"
;[left, right] = [right, left]
If your codebase omits semicolons, watch for assignment expressions that start with [ or (. Automatic semicolon insertion can surprise you.
Spread syntax is shallow
const original = {
name: "FreeMac",
settings: { theme: "dark" },
}
const copy = { ...original }
copy.name = "Other"
copy.settings.theme = "light"
Changing copy.name does not affect original.name, but changing copy.settings.theme also changes original.settings.theme, because the nested object reference is shared.
Array spread, slice(), Array.from(), and Object.assign() are also shallow copy tools.
For immutable nested updates, copy each changed level:
const next = {
...original,
settings: {
...original.settings,
theme: "light",
},
}
structuredClone
Modern browsers and Node.js support structuredClone:
const source = {
createdAt: new Date(),
tags: new Set(["Mac", "Free"]),
map: new Map([["theme", "dark"]]),
nested: { enabled: true },
}
const cloned = structuredClone(source)
It supports many built-in types and circular references. It can also work with transferable objects. But functions, DOM nodes, and class instances with custom behavior should not be treated as ordinary business data.
Before cloning, ask whether you really need a full copy of the entire tree. Large deep copies cost time and memory.
Limits of JSON copying
This pattern is common:
const copied = JSON.parse(JSON.stringify(value))
It is only safe for data that is intentionally JSON-compatible. It loses or changes many values:
Datebecomes a string.undefined, functions, and symbols may disappear.MapandSetdo not round-trip as their original types.BigIntcannot be serialized directly.- Circular references throw.
NaNandInfinitydo not preserve their original meaning.
If the data is already meant for a JSON API, this may be acceptable. Do not wrap it as a generic deepClone.
Why custom deep clone functions are risky
A simple recursive clone usually misses:
- Circular references.
- Property descriptors and non-enumerable properties.
- Symbol keys.
Date,RegExp,Map,Set, and ArrayBuffer.- Sparse arrays and prototypes.
- Getter side effects.
Write a custom clone only for a well-defined business model and test that exact model. Do not promise to clone arbitrary JavaScript values.
Destructuring is not copying everything
const { profile } = user
This assigns the profile reference to a new variable. It does not create a new profile object.
Object rest creates a new outer object, but nested values are still shared:
const { id, ...withoutId } = user
Related FreeMac guides
- For array transformations and iteration, read JavaScript Array Methods: map, splice, sort, and for...of.
- For safe nested access, read JavaScript Optional Chaining: When to Use
?.. - For task and microtask ordering, see JavaScript Event Loop: Tasks, Microtasks, and Rendering.
Continue reading
JavaScript Event Loop: Tasks, Microtasks, and Rendering
Understand the browser event loop through the call stack, tasks, microtasks, Promise callbacks, queueMicrotask, async/await, rendering opportunities, and long tasks.
7 min readWhy var and let Behave Differently in for Loops
Use the classic JavaScript closure example to understand why var in a for loop prints 3 3 3, while let creates per-iteration bindings and prints 0 1 2.
8 min readJavaScript var vs let: Function Scope, Block Scope, and Closures
Understand the difference between var and let in JavaScript through function scope, block scope, for loops, closures, and why let avoids the classic 3 3 3 result.
Subscribe to FreeMac
Weekly picks: free Mac software reviews, trusted source updates, alternatives, and low-friction guides.