Javascript 克隆:JSON.parse(JSON.stringify(x)) 的最快替代方法是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7914968/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Cloning: what's the fastest alternative to JSON.parse(JSON.stringify(x))?
提问by fadedbee
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, but I haven't found it yet.
必须有更好的/内置的方法来对对象/数组执行深度克隆,但我还没有找到。
Any ideas?
有任何想法吗?
采纳答案by Raynos
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.
让我们假设一个方法deepClone(a)
应该返回 b 的“深度克隆”。
Now a "deep clone" is an object with the same [[Prototype]] and having all the own properties cloned over.
现在,“深度克隆”是具有相同 [[Prototype]] 并克隆了所有自己的属性的对象。
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.
当然,将元数据附加到 [[Writable]] 和 [[Enumerable]] 等属性上是完整的。如果它不是一个对象,我们只会返回它。
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 lotof edge cases.
对于很多边缘情况,这将失败。
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.
如您所见,您通常无法在不破坏对象的特殊属性(如.length
数组)的情况下深度克隆对象。要解决这个问题,您必须Array
单独处理,然后单独处理每个特殊对象。
What do you expect to happen when you do deepClone(document.getElementById("foobar"))
?
当你这样做时,你期望发生什么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)
);
};
回答by AleC
I was actually comparing it against angular.copy
我实际上是在比较它 angular.copy
You can run the JSperf test here: https://jsperf.com/angular-copy-vs-json-parse-string
您可以在此处运行 JSperf 测试: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
回答by AleC
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
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.
在我疯狂的 javascript 黑客的疯狂科学家实验室中,我一直在使用基本实现来序列化整个 javascript 上下文,包括来自 Chromium 的整个 DOM,通过 websocket 将其发送到 Node 并成功重新序列化。唯一有问题的循环问题是缓和 navigator.mimeTypes 和 navigator.plugins 循环相互推到无穷大,但很容易解决。
(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.
JSONFu 使用创建代表更复杂数据类型的符号的策略。就像 MoreSigil 一样,它说该项目是缩写的,并且可以请求更深的 X 级。重要的是要了解,如果您正在序列化所有内容,那么将其恢复到原始状态显然要复杂得多。我一直在尝试各种事情,看看什么是可能的,什么是合理的,最终什么是理想的。对我来说,目标比大多数需求更吉祥,因为我试图将两个不同且同时发生的 javascript 上下文合并为一个合理的近似值。或者确定在不引起性能问题的同时公开所需功能的最佳折衷方案。
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.
我在此过程中编写了一个简洁的 hacky 函数,将您传递给它的对象的所有属性分类为特定类别。创建它的目的是能够在 Chrome 中传递一个 window 对象,并让它吐出按序列化所需的内容组织的属性,然后在远程上下文中恢复它们。也可以在没有任何预设备忘单列表的情况下实现这一点,就像一个完全愚蠢的检查器,它通过用棍子刺激传递的值来做出决定。这只是在 Chrome 中设计和检查过的,并不是生产代码,但它是一个很酷的样本。
// 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;
};