克隆:什么是JSON.parse(JSON.stringify(x))的最快替代品?

时间:2011-10-27 11:04:26

标签: javascript

的最快替代方案
JSON.parse(JSON.stringify(x))

必须有一个更好/内置的方法来对对象/数组执行深度克隆,但我还没有找到它。

有什么想法吗?

3 个答案:

答案 0 :(得分:16)

不,没有构建深度克隆对象的方法。

深度克隆是一件难以处理的事情。

让我们假设方法deepClone(a)应该返回b的“深度克隆”。

现在,“深度克隆”是一个具有相同[[Prototype]]并且克隆了所有属性的对象。

对于克隆过的每个clone属性,如果它具有可以克隆的属性,则以递归方式执行此操作。

当然是保持元数据附加到[[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;
};

对于边缘情况的批次,这将失败。

Live Example

正如您所看到的,在不破坏其特殊属性(如数组中的.length)的情况下,您无法深入克隆对象。要解决这个问题,你必须单独处理Array,然后单独处理每个特殊对象。

当您deepClone(document.getElementById("foobar"))时,您期望发生什么?

另外,浅的克隆很容易。

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)
    );
};

答案 1 :(得分:5)

我实际上是将它与angular.copy

进行比较

您可以在此处运行JSperf测试: https://jsperf.com/angular-copy-vs-json-parse-string

我正在比较:

myCopy = angular.copy(MyObject);

VS

myCopy = JSON.parse(JSON.stringify(MyObject));

这是我可以在所有计算机上运行的所有测试中最重要的一项 enter image description here

答案 2 :(得分:0)

循环引用实际上不是问题。我的意思是他们,但这只是保持适当记录的问题。无论如何快速回答这个问题。检查一下:

https://github.com/greatfoundry/json-fu

在疯狂的javascript hackery的疯狂科学家实验室中,我一直在使用基本实现来序列化整个javascript上下文,包括来自Chromium的整个DOM,通过websocket将其发送到Node并成功地重新序列化。唯一存在问题的循环问题是使用delayo 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使用创建Sigils的策略来表示更复杂的数据类型。就像一个MoreSigil,它说该项目是缩写的,并且可以请求更深的X级别。重要的是要理解,如果你将一切都序列化,那么将它恢复到原始状态显然会更复杂。我一直在尝试各种各样的事情,看看什么是可能的,什么是合理的,最终是什么是理想的。对我来说,目标比大多数需要更加吉祥,因为我试图将两个截然不同的同步javascript语境合并到单个上下文的合理近似中。或者在不引起性能问题的情况下确定在暴露所需功能方面最佳折衷方案。当你开始寻找功能的启动时,你就会从数据序列化到远程过程调用。

我沿途编写的一个整洁的hacky函数将传递给它的对象的所有属性分类为特定的类别。创建它的目的是能够在Chrome中传递一个窗口对象,并让它按照序列化所需的内容进行组织,然后在远程上下文中恢复它们。也可以在没有任何预设的备忘单列表的情况下完成此操作,例如完全愚蠢的检查器,通过用棒刺激传递的值来进行确定。这只是在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;
};