structuredClone()
: deeply copying objects in JavaScriptSpreading is a common technique for copying objects in JavaScript:
Spreading has one significant downside – it creates shallow copies: The top levels are copied, but property values are shared.
structuredClone()
is a new function that will soon be supported by most browsers, Node.js and Deno. It creates deep copies of objects. This blog post explains how it works.
structuredClone()
available? Even though structuredClone()
is not part of ECMAScript, it was added to the platform-specific parts of many platforms and is still widely available (either now or soon):
Tips:
structuredClone()
isn’t always available in WebWorkers – check the MDN browser compatibility table for more information.structuredClone
, we can use a polyfill.One common way of copying Arrays and plain objects in JavaScript is via spreading. This code demonstrates the latter:
const obj = {id: 'e1fd960b', values: ['a', 'b']};
const clone1 = {...obj};
Alas, this way of copying is shallow. On one hand, the key-value entry clone1.id
is a copy, so changing it does not change obj
:
clone1.id = 'yes';
assert.equal(obj.id, 'e1fd960b');
On the other hand, the Array in clone1.values
is shared with obj
. If we change it, we also change obj
:
clone1.values.push('x');
assert.deepEqual(
clone1, {id: 'yes', values: ['a', 'b', 'x']}
);
assert.deepEqual(
obj, {id: 'e1fd960b', values: ['a', 'b', 'x']}
);
structuredClone()
Structured clone has the following typeSignature:
structuredClone(value: any): any
(This function has a second parameter which is rarely useful and beyond the scope of this blog post. I couldn’t even replicate the use case that MDN showed for it. For more information, see the MDN page for structuredClone()
.)
structuredClone()
copies objects deeply:
const obj = {id: 'e1fd960b', values: ['a', 'b']};
const clone2 = structuredClone(obj);
clone2.values.push('x');
assert.deepEqual(
clone2, {id: 'e1fd960b', values: ['a', 'b', 'x']}
);
assert.deepEqual(
obj, {id: 'e1fd960b', values: ['a', 'b']}
);
structuredClone()
copy? Primitive values can be copied:
> typeof structuredClone(true)
'boolean'
> typeof structuredClone(123)
'number'
> typeof structuredClone('abc')
'string'
Most built-in objects can be copied – even though they have internal slots:
> Array.isArray(structuredClone([]))
true
> structuredClone(/^a+$/) instanceof RegExp
true
However, when copying a regular expression, property .lastIndex
is always reset to zero.
Some built-in objects cannot be copied – structuredClone()
throws a DOMException
if we try to do so:
Demonstration of the former:
assert.throws(
() => structuredClone(function () {}), // ordinary function
DOMException
);
assert.throws(
() => structuredClone(() => {}), // arrow function
DOMException
);
assert.throws(
() => structuredClone(class {}),
DOMException
);
const objWithMethod = {
myMethod() {},
};
assert.throws(
() => structuredClone(objWithMethod.myMethod), // method
DOMException
);
assert.throws(
() => structuredClone(objWithMethod), // object with method
DOMException
);
What does the exception look like that is thrown by structuredClone()
?
try {
structuredClone(() => {});
} catch (err) {
assert.equal(
err instanceof DOMException, true
);
assert.equal(
err.name, 'DataCloneError'
);
assert.equal(
err.code, DOMException.DATA_CLONE_ERR
);
}
In the following example, we copy an instance of the class C
. The result, clone
, is not an instance of C
.
class C {}
const clone = structuredClone(new C());
assert.equal(clone instanceof C, false);
assert.equal(
Object.getPrototypeOf(clone),
Object.prototype
);
To summarize – structuredClone()
never copies the prototype chain of an object:
Object.prototype
(like plain objects).structuredClone()
doesn’t always faithfully copy the property attributes of objects:
Read on for more information.
Accessors become data properties:
const obj = Object.defineProperties(
{},
{
accessor: {
get: function () {
return 123;
},
set: undefined,
enumerable: true,
configurable: true,
},
}
);
const copy = structuredClone(obj);
assert.deepEqual(
Object.getOwnPropertyDescriptors(copy),
{
accessor: {
value: 123,
writable: true,
enumerable: true,
configurable: true,
},
}
);
Data properties of copies always have the following attributes:
writable: true,
enumerable: true,
configurable: true,
const obj = Object.defineProperties(
{},
{
accessor: {
get: function () {
return 123;
},
set: undefined,
enumerable: true,
configurable: true,
},
readOnlyProp: {
value: 'abc',
writable: false,
enumerable: true,
configurable: true,
},
}
);
const copy = structuredClone(obj);
assert.deepEqual(
Object.getOwnPropertyDescriptors(copy),
{
accessor: {
value: 123,
writable: true,
enumerable: true,
configurable: true,
},
readOnlyProp: {
value: 'abc',
writable: true,
enumerable: true,
configurable: true,
}
}
);
structuredClone()
” on MDN.clone()
vs. copy constructors” of “Deep JavaScript”