This article mainly shares with you the analysis of JavaScript deep copy performance. How to copy an object in JavaScript? This is a very simple question, but the answer is not simple.
If you don't know what it means, take a look at the following example:
function mutate(obj) { obj.a = true; } const obj = {a: false}; mutate(obj) console.log(obj.a); // 输出 true
Function mutate
changes its parameters. In the value-passing scenario, the formal parameter of the function is just a copy of the actual parameter - a copy - and the actual parameter is not changed when the function call is completed. But in JavaScript's pass-by-reference scenario, the formal parameters and actual parameters of the function point to the same object. When the formal parameters are changed inside the parameters, the actual parameters outside the function are also changed.
So in some cases, you need to retain the original object. In this case, you need to pass a copy of the original object into the function to prevent the function from changing the original object.
Object.assign()
A simple way to obtain a copy of an object is to use Object.assign(target, sources...)
. It accepts any number of source objects, enumerates all their properties and assigns them to target
. If we use a new empty object target
, then we can copy the object.
const obj = /* ... */; const copy = Object.assign({}, obj);
However this is just a shallow copy. If our objects contain other objects as their properties, they will maintain shared references, which is not what we want:
function mutateDeepObject(obj) { obj.a.thing = true; } const obj = {a: {thing: false}}; const copy = Object.assign({}, obj); mutateDeepObject(copy) console.log(obj.a.thing); // prints true
Object.assign
Method will only copy the source object Own and enumerable properties to the target object. This method uses the[[Get]]
of the source object and the[[Set]]
of the target object, so it calls the relevantgetter
andsetter
. Therefore, it assigns properties rather than just copying or defining new properties. If the merge source containsgetter
, this may make it unsuitable for merging new properties into the prototype. In order to copy a property definition (including its enumerability) to the prototype,Object.getOwnPropertyDescriptor()
andObject.defineProperty()
should be used.
So what to do now? There are several ways to create a deep copy of an object.
Note: Maybe someone mentioned the object destructuring operation, which is also a shallow copy.
JSON.parse
One of the oldest ways to create a copy of an object is to convert that object to its JSON
string representation, Then parse it back into an object. This feels a bit oppressive, but it works:
const obj = /* ... */; const copy = JSON.parse(JSON.stringify(obj));
The disadvantage here is that you create a temporary, potentially large string just to put it back into the parser. Another disadvantage is that this method cannot handle cyclic objects. And looping objects happens often. For example, when you build a tree-like data structure, a node refers to its parent, which in turn refers to its children.
const x = {}; const y = {x}; x.y = y; // Cycle: x.y.x.y.x.y.x.y.x... const copy = JSON.parse(JSON.stringify(x)); // throws!
In addition, such as Map
, Set
, RegExp
, Date
, ArrayBuffer
and Other built-in types are lost when serialized.
Structured cloning is an existing algorithm for transferring values from one place to another. For example, it is used whenever you call postMessage to send a message to another window or WebWorker. The nice thing about structured cloning is that it handles cyclic objects and supports a large number of built-in types. The problem is, at the time of writing this article, the algorithm is not available directly, only as part of other APIs. I guess we should know what's included, shouldn't we. . .
As I said, the structured cloning algorithm works as long as you call postMessage
. We can create a MessageChannel
and send messages. On the receiving end, the message contains a structured clone of our original data object.
function structuralClone(obj) { return new Promise(resolve => { const {port1, port2} = new MessageChannel(); port2.onmessage = ev => resolve(ev.data); port1.postMessage(obj); }); } const obj = /* ... */; const clone = await structuralClone(obj);
The disadvantage of this method is that it is asynchronous. Although this is not a big deal, sometimes you need to use a synchronous method to deep copy an object.
If you have ever written a SPA using history.pushState(), you know that you can provide a state object to save the URL. It turns out that this state object uses structured cloning - and it's synchronous. We must be careful not to mess up the state objects used by the program logic, so we need to restore the original state after completing the cloning. To prevent any surprises, use history.replaceState() instead of history.pushState().
function structuralClone(obj) { const oldState = history.state; history.replaceState(obj, document.title); const copy = history.state; history.replaceState(oldState, document.title); return copy; } const obj = /* ... */; const clone = structuralClone(obj);
However, using the browser's engine just to copy an object feels a bit excessive. Additionally, Safari browser limits the number of replaceState calls to 100 times in 30 seconds.
After a tweet, Jeremy Banks showed me a third way to leverage structured cloning: the Notification API.
function structuralClone(obj) { return new Notification('', {data: obj, silent: true}).data; } const obj = /* ... */; const clone = structuralClone(obj);
Short and concise. I like it!
However, it requires the permission mechanism inside the browser, so I suspect it is very slow. For some reason, Safari always returns undefined
.
I want to measure which method is the most performant. In my first (naive) attempt, I took a small JSON object and cloned the object a thousand times in different ways. Fortunately, Mathias Bynens told me that V8 has a cache when you add properties to an object. So I'm benchmarking the cache. To make sure I never hit the cache, I wrote a function that generates an object of a given depth and width with a random key name and reran the test.
Here are the performance of different technologies in Chrome, Firefox and Edge. The lower the better.
#CONCLUSIONSo what do we get from this?
JSON.parse(JSON.stringify()) to get the most Fast cloning performance, which really surprised me.
MessageChannel is your only reliable cross-browser choice.
structuredClone() function? I certainly think so, the latest HTML specification is discussing this Synchronous clone = global.structuredClone(value, transfer = []) API · Issue #793 · whatwg/html.
JQuery’s $.extend shallow copy and deep copy example analysis
Achieve deep copy in jquery Copy and shallow copy
What are shallow and deep copies in Js
The above is the detailed content of Deep understanding of JavaScript deep copy performance. For more information, please follow other related articles on the PHP Chinese website!