Deep copying with _.clone(obj, deep) #595

Closed
wants to merge 4 commits into
from

Conversation

Projects
None yet

I sometimes return copies of nested internal data structures, so deep copying is needed to avoid accidental changes by other parts of the program or by loaded plug-ins. See also issue #586 and elsewhere.

Since the _.clone() function didn't support deep copies, I thought it would be fairly straight-forward to add it without breaking the API. Not sure that the implementation is the best though.

Also fixed a few corner cases with Date, Function and RegExp objects being cloned. Not very common scenarios perhaps, but easy enough to handle.

clone_.clone(object, [deep])

Create a copy of the object. The copy is shallow, unless deep is true. This means that nested objects or arrays are copied by reference, not duplicated. Note that the prototype chain is not preserved by this function, unlike Object.create.

_.clone({name : 'moe'});
=> {name : 'moe'};

_.clone([1, 2, 3]);
=> [1, 2, 3];

Nice work... Thanks for incorporating this.

Owner

jashkenas commented May 11, 2012

So, this is a dupe of #162 and others. We want to avoid adding and incorrect / incomplete deep cloning function, where the semantics aren't well defined in JavaScript.

The implementation proposed here is great, but still would do pretty badly on instances of classes, or anything with a significant prototype, not to mention non-enumerable things.

@jashkenas jashkenas closed this May 11, 2012

@bernharduw bernharduw referenced this pull request in powmedia/backbone-deep-model Nov 19, 2012

Closed

Bugfix toJSON #28

If you are looking for deep clone you might check out lodash who have decided to include this (much needed) feature. Hurray for market forces driving innovation :)

xibxor commented Mar 5, 2013

I was looking for a clone deep and was really surprised that underscore didn't supply it. Just because an implementation can't be fully complete for all cases, doesn't mean it shouldn't be provided, very silly reason to limit a library.

Contributor

spadgos commented Mar 6, 2013

@iandrewfuchs I believe it's working on the "principle of least surprise". That is, it's better to provide nothing than to provide something which is wrong.

xibxor commented Mar 7, 2013

@spadgos Unfortunately, any implementation is going to be wrong according to @jashkenas given his reasoning of ECMAScript not explicitly defining what a clone operation should do. It is a sad state of affairs when libraries are being commandeered into ruin. An iron approval stick does nothing more than cause this library to stagnate while Lodash continues to innovate.

mgcrea commented Mar 28, 2013

This is such a must-have feature.

To me it is frustrating for newcomers that does not clearly get how references work in javascript & therefore think that _.clone is obviously deep by default (i know the docs clearly state that this is a shallow copy, but if you don't get references, you won't understand what is a shallow copy either).

Therefore, the lack of implementation must leads to thousands of unknown bugs, and a lot of time is wasted on this.

My opinion is that you should not support any non primitive class except Date, & throw an error otherwise).

100% of my need use-case is to clone options (string/numbers) objects that are 2-level deep, supporting an optional depth paramater would enable newcomers to understand how a shallow copy works.

comster commented May 28, 2013

@mgcrea I quickly ran into a bug in my logic not appreciating that "Any nested objects or arrays will be copied by reference, not duplicated." so it seems that my object/array...

[{name: "Jeff", score: 100}, {name: "underscore", score: 33}]

...Doesn't actually clone the meat! I guess I'm getting a copy of the object list order, pointing to one instance of the objects themselves.

My own fault, but it appears that I'm not the only one tripping up on this.

The implementation proposed here is great, but still would do pretty badly on instances of classes, or anything with a significant prototype, not to mention non-enumerable things. - JA

Great point. What if the documentation specified that it only worked on Objects & Arrays?

A well defined semantics would be to define a 'json' clone semantics where

 res = JSON.parse(JSON.stringify(arg))

and throw/fail if arg includes any non-serializable items such as functions.

This semantics could then be extended into copying by reference specifically functions.

cjrd commented Nov 28, 2013

@ChrisAntaki this seems sensible to me, since after all, it saves developers time (they won't have to manually extend underscores clone method to clone nested objects and arrays).

@alexanderkjeldaas Has a good point. If it was functionally equivalent to:

_.mixin({ deepClone: function(obj) { return JSON.parse(JSON.stringify(obj)); } });

Then it would satisfy most use cases.

@akre54 akre54 referenced this pull request Apr 16, 2014

Closed

Deep extend #1585

The JSON.parse(JSON.stringify trick doesn't work for everything. Example:

var homer = { name: "Homer Simpsons", sayCatchprase: () => console.log('doh') };
var bart = JSON.parse(JSON.stringify(homer));
homer.sayCatchprase(); 
// doh
bart.sayCatchprase(); 
// Uncaught TypeError: bart.sayCatchprase is not a function

@bjura bjura referenced this pull request in BrainTech/pisak2 Jun 20, 2016

Merged

a lot of fun stuff #1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment