6 ms·
Unpopular opinion: I have never needed to "deep clone" an object in JavaScript. Any time I thought I needed to do that, it was actually a code smell for a diff
by chatmasta 3y ago
Unpopular opinion:
I have never needed to "deep clone" an object in JavaScript. Any time I thought I needed to do that, it was actually a code smell for a different underlying problem.
I was glad to see the end of the "immutable everything" dogmatic obsession wrought by Redux. The code smell in that case was usually related to passing entire objects as props.
Can anyone describe a compelling use case for "deep cloning" objects in JavaScript (keep in mind _everything_ is
an object in JavaScript) that isn't dancing around some hidden complexity? IMO, if it can't be (de)serialized via JSON.stringify and JSON.parse, then you should consider why you're trying to serialize it in the first place, and why you're scared to pass around the reference to the object. (That said, structuredClone looks like a better alternative to the JSON method - but note it's still not cloning functions, so you should still be careful where/when you use it.)
- holoduke 3y agoAn object containing just data for sure having lots of usecases. Like i.e undo/redo. But deep cloning would be as simple as json.parse(json.stringify(object))
- chatmasta 3y agoYeah, agreed. And in that case you should treat the "data object" as opaque, in which case it should be sufficient to pass it by reference. Hence, if you're reaching for deepClone, it's a code smell - why can't you pass the reference? Are you mutating the data object somewhere that you shouldn't be?
- endisneigh 3y agoThere are plenty of reasons to deepclone. That being said I do share some agreement. JavaScript imo is best when you limit complex structures and stick with primitives, ideally just one layer deep, but there are situations when it can’t be helped, e.g, dealing with api calls.
- chatmasta 3y agoYep. But API calls traverse the network, and assuming that's via JSON, you're already losing functionality like Date parsing, so the litmus test of JSON.stringify/parse seems appropriate to determine if you're dealing with a true "data object." If you run into issues with things like Dates, then it's a smell indicating a potential point of divergence in different codepaths that handle the network call differently.
- endisneigh 3y agoI don’t disagree, but just responding to your comments first paragraph.
- madeofpalk 3y agoThe most common reason I've had to do it is to log objects to the console. Chrome dev tools logs a pointer to the object, so if the object is mutated after logging, but before you view/expand it (especially for nested structures), it can be very confusing! JSON.parse(JSON.stringify()) usually is good enough for this :) But yes, generally speaking I think a deep clone is a sign of a smell elsewhere. That doesn't mean it never has a place in any code, but just that if I find myself reaching for it, I usually see if there's a mistake being made elsewhere.
- dylan604 3y agoIsn't that desired? You want to see what the object looks at at the time of logging. If you need to see it after some additional code has run, you log it again.
- recursive 3y ago> You want to see what the object looks at at the time of logging Yes. That's why you need to use structuredClone. If you don't, and you mutate the object, then you'll see the new values when you expand the object.
- Akronymus 3y agoThe problem is that if you log one object multiple times, they all point to the same object. So if between two logs it was modified, it reflects the new status on the log before as well. I think you may have been confused and got how it is, vs how it ought to be the wrong way round.
- jay_kyburz 3y agoWow, I never knew this.
- dylan604 3y agoto be honest, my experience with logging objects is so simple, i've never needed to try this live look into the object. my expectation was the log is a snap shot in time, not a live look at the object. an interactive view into the object does sound interesting rather than constant log entries for each step. which is how I would have tried to do something the first time only to be confused by it. TIL.
- davidmurdoch 3y agoIn my circles, "immutable everything" was from the functional purists.
- Waterluvian 3y agoImmutable structures are beautiful when I’ve got an editor for geographic data and want a high performance undo/redo stack with basically no effort. It’s also considerably cheaper to identify when groups of polygons have changed by object identity rather than deep equality. Did this property change? Did properties in general change? Did this polygon change? Did this collection of polygons change? All using === equality checks without any manual plumbing. About ten years into doing it this way and I’ve got one regret: starting with ImmutableJS instead of Immer.
- deleted 3y ago[deleted]
- sesm 3y ago> Can anyone describe a compelling use case for "deep cloning" objects in JavaScript Sure. A library accepts an object as function argument (for example ‘params’) and then uses it inside. Modifying users object in-place would be a bad idea.
- WendyTheWillow 3y agoThe smell here is in the library, is the commenter's point. The solution would be to search for a better written library.
- _a_a_a_ 3y agoSure! Use a library that deep-clones the object first, leaving the original parameter unchanged! And now it's slower than the original library you're criticising.
- WendyTheWillow 3y agoOr use a library that doesn’t modify the input…
- _a_a_a_ 3y agoBy deep-copying it, right? Sometimes it is needed.
- WendyTheWillow 3y ago…the whole thread you’re replying to is about it not actually being necessary. To participate in this conversation you either accept that premise or disagree with it, passing it as a given is not really an option for the argument.
- jcparkyn 3y agoIf you know what modifications you need to make (hopefully the library author does), you only need to clone the relevant parts of the object (e.g. with spreading). Deep cloning still isn't usually necessary unless you're doing wierd stuff.
- compacct27 3y agoReact needs a new object for its hooks when you change them. We need to modify those objects, so deepClone + direct mutation on the clone bypasses a lot of annoying immutable object creation bs. ppl just destructure the whole thing anyways, which is actually also pretty slow
- chatmasta 3y agoReact hooks don't need to add the entire object to their dependency array; they can specify only the properties that are actually dependencies of the logic contained in the hook.
- jcparkyn 3y agoYou should check out immer for that use case ("modifying" an immutable object with imperative code).
- dtx1 3y agoI am building a card game engine in typescript. A simple AI based on monte carlo tree search requires me to copy the gamestate a lot. And it's always a list of cards that i'm modeling so I really need to deepclone arrays.
- chatmasta 3y agoCan you not serialize this state to/from JSON? I'd encourage trying to reduce the state to be JSON-serializable, and then re-initialize any "classes" after deserializarion with a .fromJSON() method (the "factory method" pattern) - in fact you can even call such .toJSON() "replacer" [0] and .fromJSON() "reviver" [1] functions in a callback passed to JSON.stringify and JSON.parse. [0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#the_replacer_parameter https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe... [1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#the_reviver_parameter https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
- dtx1 3y agoI haven't really gotten to the point where anything works but doing the serialize de/serialize combo is possible. The issue is, that I am thinking about doing this not a few hundred times but more like several million times (preferably per minute), so i'll likely have to implement several competing versions and benchmark them. The full idea is implement a deckbuilding game like magic and use genetic algorithms and montecarlo simulation to balance the card pool. But that requires millions of games with millions of simulations to work. So i'll see if it's even possible.
- chatmasta 3y agoIf you really need maximum performance then you probably don't want to deal with JS objects at all. (But you should benchmark it first... you might be surprised how far V8 gets you with JIT optimizations - although serialization will be a bottleneck whether you're using JSON or deepClone.) For example, as one potential alternative, you could invent a scheme for encoding the state in a bit array, and then find clever bitwise operations for inspecting or manipulating it. For inspiration, many chess engines store game state in a "bitboard" [0] which is a 64 bit representation of some (partial) state of the board. Of course, at this point you might not even want to be using JavaScript... maybe you could write the hot path in Rust, compile it to wasm, and then use JS for orchestration/UI. [0] https://en.wikipedia.org/wiki/Bitboard#Chess_bitboards https://en.wikipedia.org/wiki/Bitboard#Chess_bitboards
- hutzlibu 3y agoOne use case I have, which I do not like, but there is literally no other way: Communicating with workerthreads. I so much would like to pass references of big readonly objects towards them - but no, full copy it is, for every thread.
- chatmasta 3y agoYou're probably aware of this, but in some cases you might be able to use SharedArrayBuffer [0], which doesn't make a copy of the underlying data but does use structured cloning to clone the outside of the object. [0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
- hutzlibu 3y agoI only used it for experimentation so far, but I abondoned it, after I learned, that only one worker can be the active user of a SharedArrayBuffer. So they cannot be shared at the same time? Sharing seems to mean, now A uses it, than gives control back, via the main thread, then B uses it, etc (but I hope I missunderstood something there) But I need many threads to access the same data, at the same time (readonly, so no racecondition).
- bongobingo1 3y agoAh, the OnlyChild implementation of Shared.
- kaoD 3y agoAre you sure about this? You might be mixing ArrayBuffer (which is a transferrable object[0] and gets zero-copy moved) and SharedArrayBuffer. From [1]: > The structured clone algorithm accepts SharedArrayBuffer objects and typed arrays mapped onto SharedArrayBuffer objects. In both cases, the SharedArrayBuffer object is transmitted to the receiver resulting in a new, private SharedArrayBuffer object in the receiving agent (just as for ArrayBuffer). However, the shared data block referenced by the two SharedArrayBuffer objects is the same data block, and a side effect to the block in one agent will eventually become visible in the other agent. [0] https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers... [1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
- kevincox 3y agoThe saving grace here is that it destroys classes. I have often seen deep clone and deep equals as a sort of back-door API that then breaks when the implementation of the class does. The fact that this ignores prototypes makes it clear that you should only be using this for data, not "OO Objects".
- jv22222 3y ago> Can anyone describe a compelling use case for "deep cloning" objects in JavaScript Hierarchical data structures, in mindmaps, menu structures, block based notes apps, etc.
- niedzielski 3y agoI agree. I had this same problem with a tree UI model. Each node had links to both the children and the parent. Mutating any node in the tree, changes the reference of that node. Mutating the linkage to the updated node cascades to every other node in the tree.
- efortis 3y agoUndo. I use it in UI Drafter for handling Undo. For example, I create a revision of a store (a deep clone) and apply the undo frames one-by-one. Each undo frame has the setter name and its arguments. IOW, the frames contain what changed, such as: [ [BASE.setCard, 'some-id', CF.title, 'The New Title'], …more frames ] Here's a post with more details https://blog.uidrafter.com/architecture-of-a-desktop-alike-spa https://blog.uidrafter.com/architecture-of-a-desktop-alike-s...
- giankam 3y agoYep, I do the same.
- laurencerowe 3y agoPedantically, while _everything_ is an object is true for Python it’s not for JavaScript which has distinct primitive types for strings, numbers, booleans, symbols, and undefined.
- jholman 3y agoAnd null. I know that typeof(null) says 'object', but it's lying. When I say "it's lying", I mean that according to the spec, null is of type null. Also it has absolutely nothing in common with objects except for the output of typeof. Also these days there's BigInt.
- baq 3y agoJS is amazing. As in, it’s amazing that anything actually works given the internal inconsistencies in its core. (Disclaimer: I code ts/js for a living.)
- quickthrower2 3y agoIf something is immutable then you never need to clone it. Just copy the reference. If an object is immutable and you want to copy/mutate one item it then you can surgically clone it in O(how deeply nested the item to change is). Object spread being the simple case. JS makes this messy to do though! Lots of spreads and array maps or you pull in a library like Immer and now have “coloured” objects. Immutability in JS is not idiomatic so for that reason I try to avoid unless I really need to.
- ehutch79 3y agoAn edit dialog where you want to be able to cancel editing without leaving changes on the original object.
- deleted 3y ago[deleted]
- namuol 3y agoPopular opinion* at least amongst the JS developers I’ve worked with for the last 10 years or so.
- vasergen 3y agoI used it mostly when I pass an object into a function I don't control, but I want to make sure that the original object won't be changed.
- giankam 3y agoI have an application with a big data structure built as a tree of objects. With deep cloning I can: - Provide undo/redo. I just clone the whole tree. - Load/Save from/to the database part of the tree. I load it in a separate object and then deep clone in place. - Do mass modifications to a part of the tree. A clone a copy, apply modifications and the clone back in place. I could do some of the changes in place, but to optimize Vue reactivity I work on copies. I use prototypes so only lodash works for me.