【问题标题】:Exactly clone an object in javascript在javascript中精确克隆一个对象
【发布时间】:2011-01-16 16:52:48
【问题描述】:

我试图在 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

是否有任何解决方案可以在第二次警报中实现?

【问题讨论】:

  • 大卫,与所有其他克隆问题的不同之处在于我询问了如何保留对象类型属性。

标签: javascript jquery clone


【解决方案1】:

从我在 StackOverflow 上找到的一些答案中获得灵感后,我想出了一个非常灵活的函数,并且当对象或其任何子对象具有带有所需参数的构造函数时仍然可以工作(感谢到 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 上打开问题)。

【讨论】:

    【解决方案2】:

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

    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 类似的要求,这对我很有用,所以我想我会发布它,但我意识到有些人可能需要对修改后的对象进行真正的深拷贝,这不符合要求。

    【讨论】:

      【解决方案3】:

      在 Firefox 中你可以这样写:

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

      可以这样使用:

      object1 = object2.clone();
      

      在这里找到答案:source

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

      【讨论】:

        【解决方案4】:

        问题是您正在传递一个新对象以复制到“{}”。这就是为什么你会失去你的类型。我发现如果你在传入之前包装真实对象并在之后解开复制的对象,extend 将按预期保留类型。

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

        【讨论】:

        • 我认为这是最好的答案,thanx you rock MAN :)
        【解决方案5】:
        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 不会出错,则可以使用 @ 设置内部属性987654323@。如果你使用Derived.prototype = new Base() 之类的东西进行继承,你还需要使用Derived.prototype.constructor = Derived 来获得正确的构造函数。

        您可以使用$.extend( true, new obj.constructor(), obj ),但有可能构造函数创建了后来被删除的属性——即使你可以正确获取构造函数的参数——这就是为什么在进行扩展之前必须删除属性的原因。构造函数 args 错误并不重要,因为原始构造函数 args 的影响——加上从那时起对象发生的所有其他事情——都在我们正在克隆的对象中。

        【讨论】:

          【解决方案6】:

          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 的原型继承有点棘手。您需要确保您的新对象具有正确的原型,因此具有正确的构造函数。

          诚然,Javascript 让我头疼。不过,我想我没看错,来自ECMAScript language spec

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

          1. 设 obj 为新创建的原生 ECMAScript 对象。
          2. 按照8.12中的规定设置obj的所有内部方法。
          3. 将 obj 的 [[Class]] 内部属性设置为“Object”。
          4. 将 obj 的 [[Extensible]] 内部属性设置为 true。
          5. 令 proto 为使用参数 >“prototype”调用 F 的 [[Get]] 内部属性的值。
          6. 如果 Type(proto) 为 Object,则将 obj 的 [[Prototype]] 内部属性设置为 proto。
          7. 如果 Type(proto) 不是 Object,请将 obj 的 [[Prototype]] 内部属性设置为 >standard 内置 Object 原型对象,如 15.2.4 中所述。
          8. 令 result 为调用 F 的 [[Call]] 内部属性的结果,提供 >obj 作为 this 值,并提供传递给 [[Construct]] 的参数列表作为 args。
          9. 如果类型(结果)是对象,则返回结果。
          10. 返回对象。

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

          【讨论】:

          • 这是一个非常好的解决方案。另外,您可以将克隆原型的所有属性复制到克隆,因此对 myObj 的更改不会反映在 myObjClone 中。但是如果你在不同的对象上再次调用 clone 会发生什么,myObjClone 的原型会改变吗?
          • 如果你尝试“myNewObjClone = clone(myObj)”,那么不,你没有改变myObjClone的原型。 Clone 对象只存在于 clone 函数中,因此每次调用 clone(obj) 时都会得到一个新对象。这是使用闭包“隐藏”变量的示例,在本例中为对象 Clone。
          【解决方案7】:

          您是否考虑过使用克隆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
          

          【讨论】:

          • 这是一个好的开始,但是如果构造函数需要参数,则克隆会失败: var MyClass = function(param1, param2) {alert(param1.test)};
          • Tom,您希望这个clone 函数从哪里获得预期的参数?怎么知道?
          • @J-P - 这就是我的问题所在。有没有办法在不知道要克隆的对象的任何信息的情况下获得保留类型信息的精确克隆。我现在认为这是不可能的。
          猜你喜欢
          • 2011-12-19
          • 2011-04-15
          • 2011-07-18
          • 2010-10-18
          相关资源
          最近更新 更多