What\'s the fastest alternative to
JSON.parse(JSON.stringify(x))
There must be a nicer/built-in way to perform a deep clone on objects/arrays,
Cyclic references are not really an issue. I mean they are but that's just a matter of proper record keeping. Anyway quick answer for this one. Check this:
https://github.com/greatfoundry/json-fu
In my mad scientist lab of crazy javascript hackery I've been putting the basic implementation to use in serializing the entirety of the javascript context including the entire DOM from Chromium, sending it over a websocket to Node and reserializing it successfully. The only cyclic issue that is problematic is the retardo navigator.mimeTypes and navigator.plugins circle jerking one another to infinity, but easily solved.
(function(mimeTypes, plugins){
delete navigator.mimeTypes;
delete navigator.plugins;
var theENTIREwindowANDdom = jsonfu.serialize(window);
WebsocketForStealingEverything.send(theENTIREwindowANDdom);
navigator.mimeTypes = mimeTypes;
navigator.plugins = plugins;
})(navigator.mimeTypes, navigator.plugins);
JSONFu uses the tactic of creating Sigils which represent more complex data types. Like a MoreSigil which say that the item is abbreviated and there's X levels deeper which can be requested. It's important to understand that if you're serializing EVERYTHING then it's obviously more complicated to revive it back to its original state. I've been experimenting with various things to see what's possible, what's reasonable, and ultimately what's ideal. For me the goal is a bit more auspicious than most needs in that I'm trying to get as close to merging two disparate and simultaneous javascript contexts into a reasonable approximation of a single context. Or to determine what the best compromise is in terms of exposing the desired capabilities while not causing performance issues. When you start looking to have revivers for functions then you cross the land from data serialization into remote procedure calling.
A neat hacky function I cooked up along the way classifies all the properties on an object you pass to it into specific categories. The purpose for creating it was to be able to pass a window object in Chrome and have it spit out the properties organized by what's required to serialize and then revive them in a remote context. Also to accomplish this without any sort of preset cheatsheet lists, like a completely dumb checker that makes the determinations by prodding the passed value with a stick. This was only designed and ever checked in Chrome and is very much not production code, but it's a cool specimen.
// categorizeEverything takes any object and will sort its properties into high level categories
// based on it's profile in terms of what it can in JavaScript land. It accomplishes this task with a bafflingly
// small amount of actual code by being extraordinarily uncareful, forcing errors, and generally just
// throwing caution to the wind. But it does a really good job (in the one browser I made it for, Chrome,
// and mostly works in webkit, and could work in Firefox with a modicum of effort)
//
// This will work on any object but its primarily useful for sorting the shitstorm that
// is the webkit global context into something sane.
function categorizeEverything(container){
var types = {
// DOMPrototypes are functions that get angry when you dare call them because IDL is dumb.
// There's a few DOM protos that actually have useful constructors and there currently is no check.
// They all end up under Class which isn't a bad place for them depending on your goals.
// [Audio, Image, Option] are the only actual HTML DOM prototypes that sneak by.
DOMPrototypes: {},
// Plain object isn't callable, Object is its [[proto]]
PlainObjects: {},
// Classes have a constructor
Classes: {},
// Methods don't have a "prototype" property and their [[proto]] is named "Empty"
Methods: {},
// Natives also have "Empty" as their [[proto]]. This list has the big boys:
// the various Error constructors, Object, Array, Function, Date, Number, String, etc.
Natives: {},
// Primitives are instances of String, Number, and Boolean plus bonus friends null, undefined, NaN, Infinity
Primitives: {}
};
var str = ({}).toString;
function __class__(obj){ return str.call(obj).slice(8,-1); }
Object.getOwnPropertyNames(container).forEach(function(prop){
var XX = container[prop],
xClass = __class__(XX);
// dumping the various references to window up front and also undefineds for laziness
if(xClass == "Undefined" || xClass == "global") return;
// Easy way to rustle out primitives right off the bat,
// forcing errors for fun and profit.
try {
Object.keys(XX);
} catch(e) {
if(e.type == "obj_ctor_property_non_object")
return types.Primitives[prop] = XX;
}
// I'm making a LOT flagrant assumptions here but process of elimination is key.
var isCtor = "prototype" in XX;
var proto = Object.getPrototypeOf(XX);
// All Natives also fit the Class category, but they have a special place in our heart.
if(isCtor && proto.name == "Empty" ||
XX.name == "ArrayBuffer" ||
XX.name == "DataView" ||
"BYTES_PER_ELEMENT" in XX) {
return types.Natives[prop] = XX;
}
if(xClass == "Function"){
try {
// Calling every single function in the global context without a care in the world?
// There's no way this can end badly.
// TODO: do this nonsense in an iframe or something
XX();
} catch(e){
// Magical functions which you can never call. That's useful.
if(e.message == "Illegal constructor"){
return types.DOMPrototypes[prop] = XX;
}
}
// By process of elimination only regular functions can still be hanging out
if(!isCtor) {
return types.Methods[prop] = XX;
}
}
// Only left with full fledged objects now. Invokability (constructor) splits this group in half
return (isCtor ? types.Classes : types.PlainObjects)[prop] = XX;
// JSON, Math, document, and other stuff gets classified as plain objects
// but they all seem correct going by what their actual profiles and functionality
});
return types;
};
No, there is no build in way to deep clone objects.
And deep cloning is a difficult and edgey thing to deal with.
Lets assume that a method deepClone(a)
should return a "deep clone" of b.
Now a "deep clone" is an object with the same [[Prototype]] and having all the own properties cloned over.
For each clone property that is cloned over, if that has own properties that can be cloned over then do so, recursively.
Of course were keeping the meta data attached to properties like [[Writable]] and [[Enumerable]] in-tact. And we will just return the thing if it's not an object.
var deepClone = function (obj) {
try {
var names = Object.getOwnPropertyNames(obj);
} catch (e) {
if (e.message.indexOf("not an object") > -1) {
// is not object
return obj;
}
}
var proto = Object.getPrototypeOf(obj);
var clone = Object.create(proto);
names.forEach(function (name) {
var pd = Object.getOwnPropertyDescriptor(obj, name);
if (pd.value) {
pd.value = deepClone(pd.value);
}
Object.defineProperty(clone, name, pd);
});
return clone;
};
This will fail for a lot of edge cases.
Live Example
As you can see you can't deep clone objects generally without breaking their special properties (like .length
in array). To fix that you have to treat Array
seperately, and then treat every special object seperately.
What do you expect to happen when you do deepClone(document.getElementById("foobar"))
?
As an aside, shallow clones are easy.
Object.getOwnPropertyDescriptors = function (obj) {
var ret = {};
Object.getOwnPropertyNames(obj).forEach(function (name) {
ret[name] = Object.getOwnPropertyDescriptor(obj, name);
});
return ret;
};
var shallowClone = function (obj) {
return Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);
};
I was actually comparing it against angular.copy
You can run the JSperf test here: https://jsperf.com/angular-copy-vs-json-parse-string
I'm comparing:
myCopy = angular.copy(MyObject);
vs
myCopy = JSON.parse(JSON.stringify(MyObject));
This is the fatest of all test I could run on all my computers