Which is the best method for deep cloning in JavaScript?
medium.com
medium.com
is an odd way to suffix code that will throw a SyntaxError when handed a primitive type.
"cloneJSON is very slow and can’t do much. Please avoid it."
I mean, I also use it since forever.
But I might look into the winner cloneLib.
If it is really faster, it might be worth it, but I likely still won't trust it for anything complex.
>>> const z = { d: new Date() }
undefined
>>> typeof z.d
"object"
>>> const a = JSON.parse(JSON.stringify(z))
undefined
>>> typeof a.d
"string"
>>>
After the roundtrip, the property `d` is now a string, not a Date object.The following isn't applicable for pure JSON responses, but is applicable for primitive types:
An additional condition for bigint is necessary, which is a primitive type. Otherwise JSON.stringify throws:
>>> const x = { i: BigInt("0x1fffffffffffff") }
undefined
>>> typeof x.i
"bigint"
>>> const b = JSON.parse(JSON.stringify(x))
Exception: TypeError: JSON.stringify cannot serialize BigInt.
>>>
... and for the symbol primitive, which JSON.stringifys to undefined, and thus can't be JSON.parsed.It's too bad this library (and approach) never took off, but it's there to use nevertheless.
Even without iso-fns, it's not as if mid level developer can't figure out how to write a function to perform a specific operation on an ISO date string.
After over 10 years of web development, I do think this is the best known approach and is even better than storing UNIX time. Even if there were no existing libraries to help with manipulating ISO strings, I will gladly take the inconvenience in exchange for the rest of the advantages.
With ISO strings, you don't get any behavioral quirks from Date; they are totally compatible with JSON; you can store the time zone along with the time; there is nothing to stop you from changing the time zone while leaving all the other values intact; ISO strings also support durations.
Basically, they support most of what a software developer will be expected to do with times and dates but without any behavior or assumptions about the client time zone that introduce bugs like with Date. `<input type="datetime-local">` also uses ISO strings out of the box, which can be really convenient, and values from `<input type="date">` can be easily appended into an actual ISO string.
https://developer.mozilla.org/en-US/docs/Web/API/structuredC...
* Prototypes: structuredClone discards the object’s prototype chain.
* Functions: structuredClone quietly discards functions.
* Non-cloneables: structuredClone throws for some common values, e.g. Node, Error.
Similarly, cloning things like getters means you're potentially copying over closures, which means your new cloned object now has references to its source and interacting with it may mutate the source. This is a pretty serious hazard that (IMO) justifies not cloning getters/setters, at least by default.
Cloning freeze/seal status would also lower the usability of your cloning API, because now if you wanted to make a non-frozen copy of a frozen object, you can't use the clone API.
There is a reason none of the libraries tested got it "right" - nobody needs it. Or if they do, they just write their own implementation.
I haven't used Symbol in JS much, but I was under the impression that it's _supposed_ to be a sorta "interned" value (in the sense that there exists exactly one instance in existence of each Symbol value)...which would mean that cloning them isn't a concept that makes sense.
My loose awareness of this particular JS type is leaving me quite un-confident in this understanding though, so I'd love for someone to correct me where I'm wrong so I can learn more!
This article has a pretty good overview of what symbols are, usually a corner case, but good to know -
https://medium.com/intrinsic-blog/javascript-symbols-but-why...
Yes, sometimes you will want a deep copy. These are fewer and further between than online reading about it would have you think.
How is that a smell? or how would you refactor it?
If your object is "deep" because of real nested dependencies, then you should ask why your user would need to mutate it in the first place. If they want to change its internal structure then this is indicative of a code smell because your object is too big and the user cannot accomplish their goal by calling functions exposed by your API.
There was a phase in the JS ecosystem a few years ago where everything absolutely had to be immutable. This was, IMO, a horrible side effect of react developers optimizing their props for diffing during reconciliation. Libraries like immer and redux produced a lot of this zealotry.
I understand the purism appeal of "immutable everything," and I won't say it has no application, but it has been dogmatically over-applied to the point that you have people googling how to deep clone their config object with a nested function assignment because they're scared of passing a mutable object, and oh my god what if my user mutates it?
Also your object won't be that complicated in the first place, they would be treated as plain data object like struct or record. And you would not use those advanced features in objects(Not needed).
My most common use is for delta detection and json-patch based APIs.
I keep the original entity and allow the user to modify a clone.
When it's time to sync, I compare it to the original to generate a patch set to send to the server with json-patch [1] semantics.
I highly recommend the excellent fast-json-patch library [2] that makes this process a breeze.
Actually, I use its deepClone method outside of json-patch scenarios, it's so well implemented.
With setState in React, you don't necessarily have to copy to update the state. setState merges the partial object you provide with the current state.
https://reactjs.org/docs/state-and-lifecycle.html#state-upda...
In Redux reducers, however, you need a full copy.
But I just want to point out that cloning isn’t an end in itself.
The article includes a lot of judgements but it’s all context-free. Whether a clone method is good or not depends on its suitability for a purpose. E.g., whether you want non-enumerable properties cloned (if you care at all) really depends on your specific uses. Not to mention performance is very often a concern and that isn’t covered here.
But one thing not included in this page is that there are things it just doesn't serialize, even when it's actual data:
const native = { number: Number.POSITIVE_INFINITY };
const cloned = JSON.parse(JSON.stringify(native));
// unlike NaN, we can compare Number.POSITIVE_INFINITY to itself
console.log(native.number === native.number);
// but it doesn't serialize
console.log(native.number === cloned.number);
console.log(cloned);
Yields true
false
{number: null}
Oops.The first decoder was the JS eval() function, so JSON was clearly intended to be a subset of JS. Ambiguity in the spec is not a licence to deviate from JS semantics, it just means that the spec is poorly written.
The spec is not ambiguous. And a spec is a spec is a spec. You can implement it or not. But you can’t just decide “Enh… they didn’t mean it like that so it’s wrong to satisfy the spec. You should really satisfy it wrongly.”
> In other languages like Java, each class is expected to implement its own clone method
This is incorrect. There is an `Object.clone()` in Java, but it's not implemented by default for most types, implementing it is fraught with complexity, and the standard book of Java advice strongly recommends avoiding it. If you indicate that your type supports cloning (using the marker interface `Cloneable`), you still only get a shallow copy by default.Personally I just don't try to clone anything that isn't JSON-valid data, and I usually use the stringify/parse strategy. It's slow in a hot loop, but it's fast enough for the rare situations where I need to fully clone something. Most of the time, I can just use readonly types and avoid defensively cloning the whole tree. When I want to replace part of an immutable object, destructuring works fine
Hmm?
> cloneJSON is very slow and can’t do much. Please avoid it.
I always thought that javascript is fast at JSON parsing. Atleast the benchmark shows nice results: https://www.measurethat.net/Benchmarks/Show/18541/0/jsonstri...
By closure, do you mean the values of free variables at the time of the copy?
If so, the inability to do a perfect copy is applicable to regular functions at the top level, too. (For such functions, other global variables are free variables.)
Considering this, I think this is why copying a function is generally defined as just having a reference to the function that can later be used to invoke it. It does not include each copy of the function having its own copy of the function's free variables from the instant of the copy. Separate invocations of the multiple copies (i.e. references) of the function may affect the shared closure environment/free variables.
I'll take a fast deep clone for my case over one that handles all cases.