For a long time, you had to resort to workarounds and libraries to create deep copies of JavaScript values. But in the latest HTML5 specification, javaScript comes with a built-in function for deep copy, structuredClone().

Browser support:

At the time of this writing, all browsers implement this API in their grayscale versions, and Firefox has released a stable version of it in Firefox 94. In addition, Node 17 and Deno 1.14 already implement this API. You can try this feature right away.

Shallow copy

Copying an object in JavaScript is almost always a shallow copy rather than a deep copy. This means that changes to object property values are visible in both the copy and the original value.

One way to create shallow copies in JavaScript using object extension operators… :

const me = {
  name: 'the moon and the.age: 21.girlFriend: {
      name: 'Some rich woman'.age: 81}};constmeCopy = {... me};Copy the code

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

meShallowCopy.age = 22;
console.log(me.age);/ / 21
Copy the code

However, adding or changing deeply nested attributes can affect both copies and original values:

meShallowCopy.girlFriend.age = 82;
console.log(me.girlFriend.agep) / / 82
Copy the code

This expression {… Me} iterates me using the (enumerable) properties of the extended operator. It takes attribute names and values and assigns them one by one to newly created empty objects. Thus, the generated object has the same shape, but has its own copy of the list of properties and values. Values are also copied, but JavaScript values treat so-called raw values differently than non-raw values.

In JavaScript, a primitive value (primitive value, primitive data type) is not an object and has no method data. There are seven primitive data types: String, Number, BigINT, Boolean, undefined, symbol, and NULL.

Reference MDN

Non-original values are treated as references, which means that the act of copying a value is really just copying a reference to an object, resulting in shallow copy behavior.

Deep copy

The opposite of a shallow copy is a deep copy. The deep-copy algorithm also copies the attributes 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 do not 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 meDeepCopy = JSON.parse(JSON.stringify(me));
Copy the code

In fact, this is a very popular solution for V8 to actively optimize json.parse (), especially in the case above, to make JSON as fast as possible. While fast, it has some downsides and stumbling blocks:

  • Recursive data structures:JSON.stringify()It throws an error when you give it a recursive data structure. This can easily happen when using linked lists or trees.
  • Built-in types:JSON.stringify()If the value contains other JS built-in types, things like Maps, Sets, RegExps, Dates, ArrayBuffers, and other built-in types are only lost during serialization.
  • function:JSON.stringify()The function is quietly discarded.

Json.parse () has already outperformed literal object creation in V8 optimizations so far, but only when the data is small

Let obj = json. parse(‘{“name”:”zs”}’) is faster than let obj = {name:”zs”}.

Structured clone

Several deep-copy schemes already provided and implemented by JavaScript:

  • Storing JS values in IndexedDB requires some form of serialization so that it can be stored on disk and then deserialized to recover JS values.
  • Similarly, by sending a message to the WebWorkerpostMessage()JS values need to be transferred from one JS domain to another. The algorithm used for this is called “structured cloning”.

But until recently, it was not easy for developers to use these schemes to implement deep copy

But don’t worry now! The latest HTML specification exposes a function called structuredClone() that runs a structured cloning algorithm and is an easy way for developers to implement deep copies of JavaScript.

structuredClone(value, { transfer })

parameter

  • value

    Object to clone: This can be any structured clonable type.

  • Transfer the optional

    Transferable object: an array of values to be moved to a new object rather than cloned to a new object.

The return value

The value returned is a deep copy of the original value.

exception

  • DataCloneError

    This error is thrown if any part of the input value is not serialized.

const meDeepCopy = structuredClone(me);
Copy the code

Features and Limitations

Structured cloning addresses many (though not all) of the shortcomings of the json.stringify () technique. Structured clones can handle cyclic dependencies, support many built-in data types, and are more robust and faster.

However, it still has some limitations:

  • The prototype: If you usestructuredClone()Class instance, you will get a normal object as the return value, because structured cloning would discard the prototype chain of the object.
  • Functions: If your object contains functions, they will be quietly discarded.
  • Cannot be cloned: Some values are not structurally clonable, especiallyError,DOM nodeFunction. Attempting to do so will raiseDataCloneErrorThe exception.
  • Property descriptors: Setters and getters (and metadata-like functions) are not copied. For example, if an object is marked as read-only using a property descriptor, the copied object is read and write (the default configuration).
  • RegExp: The lastIndex field of the RegExp object is not retained.

If any of your use cases will have an impact on your expected results because of the above limitations, libraries like Lodash still provide custom implementations of other deep cloning algorithms that may or may not be appropriate for your use cases.

Supported types

  • Basic type except symbol
  • Boolean object
  • String object
  • Date
  • RegExp
  • Blob
  • File
  • FileList
  • ArrayBuffer
  • ArrayBufferView All arrayBuffer views, such as Int32Array.
  • ImageBitmap
  • ImageData
  • Array
  • Object Common JS Object
  • Map
  • Set

performance

Before structuredClone(). Json.parse () is the fastest option for very small data. For larger objects, techniques that rely on structured cloning are faster and faster. Given that the new structuredClone() doesn’t abuse the overhead of other apis and is more robust than json.parse (), I recommend that you make it the default method for creating deep copies.

conclusion

If you need to use deep copy in JS — perhaps because you’re using immutable data structures, or you want to make sure a function can manipulate an object without affecting the original object — you no longer need to look for solutions or search. The JS ecosystem now has structuredClone() to satisfy you. I believe it will be implemented by all browsers in the near future, but for now you have to consider its compatibility as it is just a child 😎