There is one thing that really irks me in Javascript, everything is passed by reference. Recently in a project I was working on, I had an object of widget options I needed to modify. I also needed to offer the ability to revert the changes made to this object.
The kicker is: this object is complex. It isn’t just properties and values, it has other objects inside of it and arrays. Everyone has an opinion on how you should do it, many ways to skin a cat so-to-speak.
I tried some of the various deep merge and clone methods out there, tried Lodash and the solution I ended up going with is:
var clonedWidget = (JSON.parse(JSON.stringify(originalWidget)));
The beautiful thing about this approach is that it can take an object with infinite levels of nesting, turns it into a string that represents JSON and then we parse the string to get back JSON, thus giving us a new object.
While there is a slight performance hit for large objects, it will not be noticeable unless you’re profiling your code on a low-level that most do not bother going to. This is the most straightforward and easy-to-use solution that has great performance and works great.
There could be issues with this approach that I have yet to run into. So, like all code you find on the Internet, don’t just blindly copy and paste without doing some testing first. Just because it works for me, does not mean it will work for you.
Ran into this just the other day: https://twitter.com/sitapati/status/671582382370234368
I honestly didn’t think of this approach – what a great idea!
How does it differ from the approach used by _.cloneDeep?
You should point out the problems with this approach.
It will fail if…
1. There are things in it json wont do, like…dates, regex’s, functions
2. There are circular references
Number two is an utter pain and cant cant be dealt with without a bit of work. From the code I looked at you might as well go ahead and write a deepClone function considering your going to do half the work anyways.
Number one can be dealt with using replacer functions.
Here’s something I just wrote to show how (didnt test bugger all, might have missed a type for all I know)….
function deepCopy(input){
var special=[];
function stringifyReplacer(key, value) {
if(!isJSONable(value)){
special.push(value);
return '#!'+(special.length-1);
}
return value;
}
function parseReplacer(key, value) {
if(value[0]=='#'&&value[1]=='!'){
// wonder if I need to worry about changing a functions prototype after copying...I know nothing on this, is it a concern?
return special[value.substr(2)];
}
return value;
}
function isJSONable(arg){
valids={'object':true,
'boolean':true,
'number':true,
'string':true,
'[object RegExp]':true,
'[object Date]':true,
'[object Error]':true
}
var type=typeof arg;
var stringed=Object.prototype.toString.call(arg);
return !(arg instanceof Error) && valids[type] && !valids[stringed];
}
return JSON.parse(JSON.stringify(input, stringifyReplacer),parseReplacer);
}
//test
var foo = {foundation: function(){}, model: /a/, week: 45, transport: "car", month: 7};
var a = deepCopy(foo);
console.log(a)
sorry….no idea how to post code in a comment here ;P
This is definitely worthy of note, thank you for commenting.
The approach detailed in the post is essentially just for simple objects, which might also have varied levels of deepness. If you’re working with more complex values inside of your objects they will definitely be lost. For my use-case scenario, the JSON trick works as I only had simple keys and values inside of my objects.
It seems that the performance is not so bad:
* http://jsperf.com/object-clone-jquery-vs-underscore-vs-lodash/3
* https://jsperf.com/deep-copy-vs-json-stringify-json-parse/30
hilarious that as a JS noob, this is actually the solve I came up with. Awesome.