如何在JavaScript中进行深入克隆

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (27)

你如何深入克隆一个Javascript对象?

我知道有基于框架的各种功能JSON.parse(JSON.stringify(o))$.extend(true, {}, o)但我不想使用这样的框架。

创建深度克隆的最优雅或有效的方式是什么?

我们确实关心像克隆数组这样的边缘情况。不打破原型链,处理自我参照。

我们不关心支持DOM对象的复制等,因为.cloneNode存在这个原因。

由于我主要想要使用深度克隆,因此node.js使用V8引擎的ES5功能是可以接受的。

在任何人提出建议之前,让我先提一下,通过从对象原型继承并克隆它来创建副本有明显的区别。前者弄乱了原型链。

在阅读你的答案后,我发现了一个令人讨厌的发现,即克隆整个物体是一个非常危险和困难的游戏。以下面的基于闭包的对象为例

var o = (function() {
     var magic = 42;

     var magicContainer = function() {
          this.get = function() { return magic; };
          this.set = function(i) { magic = i; };
     }

      return new magicContainer;
}());

var n = clone(o); // how to implement clone to support closures

有没有办法编写克隆对象的克隆函数,在克隆时具有相同的状态,但不能o在JS中编写JS解析器时改变状态。

现在不再需要这种功能了。这仅仅是学术兴趣。

提问于
用户回答回答于

这真的取决于你想克隆什么。这是一个真正的JSON对象还是JavaScript中的任何对象?如果你想做任何克隆,它可能会让你陷入麻烦。哪个麻烦?我将在下面解释它,但首先是克隆对象文字,任何原语,数组和节点的代码示例。

function clone(item) {
    if (!item) { return item; } // null, undefined values check

    var types = [ Number, String, Boolean ], 
        result;

    // normalizing primitives if someone did new String('aaa'), or new Number('444');
    types.forEach(function(type) {
        if (item instanceof type) {
            result = type( item );
        }
    });

    if (typeof result == "undefined") {
        if (Object.prototype.toString.call( item ) === "[object Array]") {
            result = [];
            item.forEach(function(child, index, array) { 
                result[index] = clone( child );
            });
        } else if (typeof item == "object") {
            // testing that this is DOM
            if (item.nodeType && typeof item.cloneNode == "function") {
                var result = item.cloneNode( true );    
            } else if (!item.prototype) { // check that this is a literal
                if (item instanceof Date) {
                    result = new Date(item);
                } else {
                    // it is an object literal
                    result = {};
                    for (var i in item) {
                        result[i] = clone( item[i] );
                    }
                }
            } else {
                // depending what you would like here,
                // just keep the reference, or create new object
                if (false && item.constructor) {
                    // would not advice to do that, reason? Read below
                    result = new item.constructor();
                } else {
                    result = item;
                }
            }
        } else {
            result = item;
        }
    }

    return result;
}

var copy = clone({
    one : {
        'one-one' : new String("hello"),
        'one-two' : [
            "one", "two", true, "four"
        ]
    },
    two : document.createElement("div"),
    three : [
        {
            name : "three-one",
            number : new Number("100"),
            obj : new function() {
                this.name = "Object test";
            }   
        }
    ]
})

现在,我们来讨论克隆REAL对象时可能遇到的问题。我现在正在谈论,关于你通过做类似的东西创建的对象

var User = function(){}
var newuser = new User();

当然,你可以克隆它们,这不是问题,每个对象都暴露了构造函数属性,并且你可以使用它来克隆对象,但它不会总是有效。你也可以for in在这个对象上做简单的事情,但它走向了同一个方向 - 麻烦。我还在代码中包含了克隆功能,但它被if( false )语句排除。

那么,为什么克隆会是一种痛苦呢?那么,首先,每个对象/实例都可能有一些状态。你永远不能确定你的对象没有例如一个私有变量,如果是这种情况,通过克隆对象,你只是打破了状态。

想象一下,没有国家,没关系。那么我们还有另一个问题。通过“构造函数”方法克隆会给我们另一个障碍。这是一个参数依赖。你永远无法确定,创造这个东西的人没有做过某种事

new User({
   bike : someBikeInstance
});

如果是这种情况,那么你运气不好,有些BikeInstance可能是在某种情况下创建的,而且这种情况对于克隆方法来说是未知的。

那么该怎么办?你仍然可以做for in解决方案,并将这些对象视为普通的对象文字,但也许这是一个想法,根本不克隆这些对象,只是传递这个对象的引用?

另一个解决方案是 - 您可以设置一个约定,即所有必须克隆的对象都应该自己实现这个部分,并提供适当的API方法(如cloneObject)。什么cloneNode是为DOM做的事情。

用户回答回答于

非常简单的方式,可能太简单了:

var cloned = JSON.parse(JSON.stringify(objectToClone));

扫码关注云+社区