头图

Deep copy in JavaScript using structured clone

四六十
中文

For a long time, you had to resort to workarounds and libraries to create deep copies of JavaScript values. Now js provides structuredClone() a built-in function for deep copying.

Browser Support:

shallow copy

Copying a value in JavaScript is almost a shallow copy, not a deep copy. This means that changes to deeply nested values will be visible in both the copy and the original.
A way to create a shallow copy in JavaScript using the object spread operator

const myOriginal = {
  someProp: "with a string value",
  anotherProp: {
    withAnotherProp: 1,
    andAnotherProp: true
  }
};

const myShallowCopy = {...myOriginal};

Adding or changing properties directly on the shallow copy only affects the copy, not the original value:

myShallowCopy.aNewProp = "a new value";
console.log(myOriginal.aNewProp)
// ^ logs `undefined`

However, adding or changing deeply nested properties affects the original and copied values:

myShallowCopy.anotherProp.aNewProp = "a new value";
console.log(myOriginal.anotherProp.aNewProp) 
// ^ logs `a new value`

The object spread operator iterates over all enumerable properties of myOriginal. It takes the property name and value and assigns them one by one to a newly created empty object. So the resulting object is identical in shape but has its own copy of the list of properties and values. These values are also copied, but JavaScript values handle so-called primitive types differently than non-primitive types.
Non-primitive types are treated as references, which means that the act of copying a value is really just copying a reference to the same underlying object, resulting in shallow copy behavior.

deep copy

The opposite of shallow copy is deep copy. The deep copy algorithm also copies the properties of an object one by one, but recursively calls itself when it finds a reference to another object, creating a copy of that object at the same time. This is important to ensure that two pieces of code don't accidentally share an object and unknowingly manipulate each other's state.
There used to be no easy or good way to create deep copies of values in JavaScript. Many people rely on third-party libraries, such as Lodash's cloneDeep() function. Arguably the most common solution to this problem is a JSON-based hack:

const myDeepCopy = JSON.parse(JSON.stringify(myOriginal));

In fact, this is a very popular workaround, and V8 aggressively optimizes JSON.parse() , especially the pattern above, to make it as fast as possible. While fast, it also has some drawbacks:

  • Recursive data structures: JSON.stringify() will throw an error when you serialize a recursive data structure. This can easily happen when working with linked lists or trees.
  • Built-in types: JSON.stringify() will also report an error if the value contains other JS keywords such as Map, Set, Date, RegExp or ArrayBuffer.
  • Functions: JSON.stringify() may drop functions.

structured clone

Browsers already need the ability to create deep copies of JavaScript values in several places: Storing a JS value in IndexedDB requires some form of serialization so that it can be stored on disk and then deserialized to restore the JS value. Similarly, sending a message to a WebWorker postMessage() requires transferring JS values from one JS realm to another.
The HTML specification was modified to expose a function called structuredClone() that runs the algorithm entirely, as a means for developers to easily create deep copies of JavaScript values.

const myDeepCopy = structuredClone(myOriginal);

Features and Limitations

Structured cloning addresses many, though not all, of the shortcomings of this JSON.stringify() technique. Structured clones can handle cyclic data structures, support many built-in data types, and are generally more robust and generally faster.
However, it still has some limitations that might catch you off guard:

  • Prototypes: If structuredClone() is used with a class instance, you will get a plain object as the return value, because structured clone discards the object's prototype chain.
  • Functions: Functions are also not supported.
  • Non-cloneables: Some values are not structurally cloneable, most notably Error and DOM nodes. It will cause structuredClone() to throw an exception.

If any of these limitations break your use case, libraries like Lodash still provide custom implementations of other deep-clone algorithms that may or may not be suitable for your use case.

阅读 724

前端
关注用户体验,分享前端技术
110 声望
17 粉丝
0 条评论
110 声望
17 粉丝
文章目录
宣传栏