在javascript中完全克隆一个对象

时间:2010-02-14 13:46:38

标签: javascript jquery clone

我试图在javascript中完全克隆一个对象。我使用jquery知道以下解决方案:

var newObject = jQuery.extend({}, oldObject);
// Or
var newObject = jQuery.extend(true, {}, oldObject);

但问题是,对象类型丢失了:

var MyClass = function(param1, param2) {
    alert(param1.a + param2.a);
};
var myObj = new MyClass({a: 1},{a: 2});
var myObjClone = jQuery.extend(true, {}, myObj);
alert(myObj instanceof MyClass);      // => true
alert(myObjClone instanceof MyClass); // => false

第二次警报是否有任何解决办法?

7 个答案:

答案 0 :(得分:12)

jQuery.extend不希望你使用instanceof运算符。它正在做一个光荣复杂的副本,而不是一个真正的克隆。循环使用元素是不够的。此外,调用构造函数不是最好的,因为你将失去你的参数。试试这个:

var MyClass = function(param1, param2) {
    alert(param1.a + param2.a);
    this.p1 = param1;
    this.p2 = param2;
};

function Clone() { }
function clone(obj) {
    Clone.prototype = obj;
    return new Clone();
}

var myObj = new MyClass({a: 1},{a: 2});
var myObjClone = clone(myObj);
alert(myObj instanceof MyClass);      // => true
alert(myObjClone instanceof MyClass); // => true
console.log(myObj);       //note they are
console.log(myObjClone)   //exactly the same

请注意,由于您的原型现在指向原始(myObj),myObj的任何更改都将反映在myObjClone中。 Javascript的原型继承有点棘手。您需要确保新对象具有正确的原型,因此需要正确的构造函数。

Admitadly,Javascript让我头疼。不过,我认为我正在阅读ECMAScript language spec

  13.2.2 [[建筑]]
  当使用可能为空的参数列表调用Function对象F的[[Construct]]内部方法时,将执行以下步骤:

     
      
  1. 让obj成为新创建的本机ECMAScript对象。
  2.   
  3. 按照8.12中的说明设置obj的所有内部方法。
  4.   
  5. 将obj的[[Class]]内部属性设置为“Object”。
  6.   
  7. 将obj的[[Extensible]]内部属性设置为true。
  8.   
  9. 让proto成为使用参数>“prototype”调用F的[[Get]]内部属性的值。
  10.   
  11. 如果Type(proto)是Object,则将obj的[[Prototype]]内部属性设置为proto。
  12.   
  13. 如果Type(proto)不是Object,则将obj的[[Prototype]]内部属性设置为>标准内置Object原型对象,如15.2.4中所述。
  14.   
  15. 让结果成为调用F的[[Call]]内部属性的结果,提供> obj作为此值,并将传递给[[Construct]]的参数列表作为args提供。
  16.   
  17. 如果Type(result)是Object,则返回结果。
  18.   
  19. 返回obj。
  20.   

This person似乎比我更了解这个概念。 K,我现在回到java,我游泳的次数比我下沉的还多:)。

答案 1 :(得分:4)

您是否考虑过使用克隆function suggested here

function clone(obj){
    if(obj == null || typeof(obj) != 'object'){
        return obj;
    }

    var temp = new obj.constructor();
    for(var key in obj){
        temp[key] = clone(obj[key]);
    }
    return temp;
}

var MyClass = function(param1, param2) {};
var myObj = new MyClass(1,2);
var myObjClone = clone(myObj);
alert(myObj instanceof MyClass);      // => true
alert(myObjClone instanceof MyClass); // => true

答案 2 :(得分:2)

从我在StackOverflow上找到的一些答案中汲取灵感之后,我想出了一个非常灵活的函数,当对象或其任何子对象具有带有必需参数的构造函数时,它仍然可以工作(谢谢to Object.create)。

(感谢Justin McCandless,现在也支持循环引用。)

//If Object.create isn't already defined, we just do the simple shim, without the second argument,
//since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

/**
 * Deep copy an object (make copies of all its object properties, sub-properties, etc.)
 * An improved version of http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
 * that doesn't break if the constructor has required parameters
 * 
 * It also borrows some code from http://stackoverflow.com/a/11621004/560114
 */ 
function deepCopy(src, /* INTERNAL */ _visited, _copiesVisited) {
    if(src === null || typeof(src) !== 'object'){
        return src;
    }

    //Honor native/custom clone methods
    if(typeof src.clone == 'function'){
        return src.clone(true);
    }

    //Special cases:
    //Date
    if(src instanceof Date){
        return new Date(src.getTime());
    }
    //RegExp
    if(src instanceof RegExp){
        return new RegExp(src);
    }
    //DOM Element
    if(src.nodeType && typeof src.cloneNode == 'function'){
        return src.cloneNode(true);
    }

    // Initialize the visited objects arrays if needed.
    // This is used to detect cyclic references.
    if (_visited === undefined){
        _visited = [];
        _copiesVisited = [];
    }

    // Check if this object has already been visited
    var i, len = _visited.length;
    for (i = 0; i < len; i++) {
        // If so, get the copy we already made
        if (src === _visited[i]) {
            return _copiesVisited[i];
        }
    }

    //Array
    if (Object.prototype.toString.call(src) == '[object Array]') {
        //[].slice() by itself would soft clone
        var ret = src.slice();

        //add it to the visited array
        _visited.push(src);
        _copiesVisited.push(ret);

        var i = ret.length;
        while (i--) {
            ret[i] = deepCopy(ret[i], _visited, _copiesVisited);
        }
        return ret;
    }

    //If we've reached here, we have a regular object

    //make sure the returned object has the same prototype as the original
    var proto = (Object.getPrototypeOf ? Object.getPrototypeOf(src): src.__proto__);
    if (!proto) {
        proto = src.constructor.prototype; //this line would probably only be reached by very old browsers 
    }
    var dest = object_create(proto);

    //add this object to the visited array
    _visited.push(src);
    _copiesVisited.push(dest);

    for (var key in src) {
        //Note: this does NOT preserve ES5 property attributes like 'writable', 'enumerable', etc.
        //For an example of how this could be modified to do so, see the singleMixin() function
        dest[key] = deepCopy(src[key], _visited, _copiesVisited);
    }
    return dest;
}

此功能是我simpleOO库的一部分;将在那里进行任何错误修复或增强(如果你发现了一些错误,请随时在github上打开一个问题)。

答案 3 :(得分:1)

function clone( obj ) {  
    var target = new obj.constructor();  
    for ( var key in target ) { delete target[key]; }  
    return $.extend( true, target, obj );  
}  

$。extend不能复制所有不可见的内部属性(有些在firefox中可见),但是如果obj.constructor是正确的,并且没有args就不会出错,可以设置内部属性与new obj.constructor()。如果您使用Derived.prototype = new Base()之类的内容进行继承,则还需要使用Derived.prototype.constructor = Derived进行继承,以使构造函数正确。

你可以做$.extend( true, new obj.constructor(), obj ),但是构造函数可能会创建后来被删除的属性 - 即使你可以正确地获得构造函数args - 这就是在进行扩展之前必须删除属性的原因。构造函数args是错误的并不重要,因为原始构造函数args的效果 - 加上从那时起发生在对象上的所有其他东西 - 都在我们正在克隆的对象中。

答案 4 :(得分:1)

问题是您传入的新对象要复制到“{}”。这就是你失去类型的原因。我发现如果在传入真实对象之前将其包装起来并在之后解包复制的对象,则extend将按预期保留该类型。

function clone(obj)
{
    var wrappedObj = { inner: obj };
    var newObject = jQuery.extend(true, {}, wrappedObj);
    newObject = newObject.inner;
    return newObject;
}

答案 5 :(得分:0)

在Firefox中你可以写:

Object.prototype.clone = function() {
  return eval(uneval(this));
}

可以像:

一样使用
object1 = object2.clone();

在此处找到答案:source

但这只是Firefox的魔力。其他浏览器可能会崩溃。

答案 6 :(得分:0)

这是一种替代解决方案,它不会完全复制或克隆任何内容,但应该提供所需的结果。

var myObj = new MyClass({a: 1},{a: 2});
var myObjCreator = MyClass.bind(this, {a: 1},{a: 2});
var myObjClone = new myObjCreator();

这使用Javascript的bind函数创建一个自动将给定参数传递给MyClass构造函数的对象。

我有与OP类似的要求,这对我有用,所以我想我会发布它,虽然我发现有些人可能需要在修改后的对象上进行真正的深层复制,但这不符合要求。