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.
Table of contents:
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”