2010-02-14 20 views
20

Traté de clonar exactamente un objeto en javascript. Sé que la siguiente solución usando jQuery:Exactamente clonar un objeto en javascript

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

pero el problema con esto es, que el tipo de objetos se pierde:

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 

¿Hay alguna solución para conseguir cierto en la segunda alerta?

+1

David, la diferencia con todas las otras preguntas de clonación es que pregunté cómo conservar la propiedad de tipo de objetos. – Tom

Respuesta

12

jQuery.extend no espera que utilice el operador instanceof. Está haciendo una copia gloriosamente complicada, y no un verdadero clon. Looping a través de los elementos no es suficiente. Además, llamar al constructor no es lo mejor porque perderás tus argumentos. Prueba esto:

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 

Tenga en cuenta que, dado que el prototipo ahora apunta de nuevo a la original (myObj), cualquier cambio en myObj reflejarán en myObjClone. La herencia prototípica de Javascript es un poco complicada. Debe asegurarse de que su nuevo objeto tenga el prototipo correcto y, por lo tanto, el constructor correcto.

Admitadly, Javascript hace que me duela la cabeza. Aún así, creo que estoy leyendo este derecho, desde el ECMAScript language spec:

13.2.2 [[Construct]]
Cuando el [[Construct]] método interno para un objeto función f se llama posiblemente con una lista vacía de argumentos, se realizan los siguientes pasos:

  1. Deje que obj sea un objeto ECMAScript nativo recién creado.
  2. Establezca todos los métodos internos de obj como se especifica en 8.12.
  3. Establezca la propiedad interna [[Class]] de obj en "Objeto".
  4. Establezca la propiedad interna [[Extensible]] de obj en verdadero.
  5. Deje proto ser el valor de llamar a la propiedad interna [[Get]] de F con argumento> "prototipo".
  6. Si Tipo (proto) es Objeto, establezca la propiedad interna [[Prototipo]] de obj en proto.
  7. Si Type (proto) no es Object, establezca la propiedad interna [[Prototype]] de obj en el objeto prototipo de objeto estándar incorporado como se describe en 15.2.4.
  8. Deje que el resultado sea el resultado de llamar a la propiedad interna de [[Llamar]] de F, proporcionando> obj como este valor y proporcionando la lista de argumentos pasada a [[Construir]] como argumentos.
  9. Si Tipo (resultado) es Objeto, entonces devuelve el resultado.
  10. Retorno obj.

This person parece entender el concepto mucho mejor que yo. K, voy a volver a Java ahora donde nado más de lo que me hundo :).

+0

Thas es una solución muy agradable. Además, podría copiar todas las propiedades del prototipo de clones al clon, de modo que los cambios en myObj no se reflejarán en myObjClone. Pero, ¿qué pasa si llamas de nuevo a clon en un objeto diferente, se modificará el prototipo de myObjClone? – Tom

+0

Si prueba "myNewObjClone = clone (myObj)", entonces no, no cambia el prototipo de myObjClone. El objeto Clone solo vive dentro de la función de clonación, por lo que obtienes uno nuevo cada vez que llamas a clone (obj). Ese es un ejemplo del uso de cierre para "ocultar" una variable, en este caso, el objeto Clonar. – Stephano

4

¿Ha considerado utilizar el clon 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 
+0

Es un buen comienzo, pero el clon falla si el constructor requiere parámetros: var MyClass = function (param1, param2) {alert (param1.prueba)}; – Tom

+0

Tom, ¿de dónde esperas que esta función 'clon' obtenga los argumentos esperados? ¿Cómo es saber? – James

+0

@ J-P: de eso se trata mi pregunta. ¿Hay alguna manera de obtener un clon exacto preservando la información del tipo sin saber nada sobre el objeto para clonar? Ahora supongo que es imposible. – Tom

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

$.extender no puede copiar todas las propiedades internas que no son visibles (algunas son visibles en firefox), pero si obj.constructor es correcto y no tendrá fallas sin argumentos, las propiedades internas se pueden establecer con new obj.constructor(). Si haces herencia con algo como Derived.prototype = new Base(), también necesitarás seguir eso con Derived.prototype.constructor = Derived para obtener el constructor correcto.

Puede hacer $.extend(true, new obj.constructor(), obj), pero es posible que el constructor cree propiedades que luego fueron eliminadas, incluso si pudiera obtener las args del constructor correctas, es por eso que las propiedades deben borrarse antes de hacer la extensión. No importa que los argumentos del constructor sean incorrectos ya que los efectos de los argumentos del constructor original, más todo lo demás que le ha sucedido al objeto desde entonces, están en el objeto que estamos clonando.

1

El problema es que está pasando un nuevo objeto para copiar a '{}'. Es por eso que pierdes tu tipo. Descubrí que si envuelve el objeto real antes de pasarlo y desenvolver el objeto copiado después, extender conservará el tipo como se esperaba.

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

Creo que esta es la mejor respuesta, gracias Rock Rock :) –

2

Después de tomar la inspiración de algunas de las respuestas que he encontrado en StackOverflow, yo he llegado con una función que es bastante flexible, y todavía funciona cuando el objeto o cualquiera de sus sub-objetos tiene un constructor con parámetros necesarios (gracias a Object.create). (. Gracias a Justin McCandless esto ahora es compatible con las referencias cíclicas así)

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

Esta función es parte de mi biblioteca simpleOO; cualquier corrección de errores o mejoras se realizarán allí (no dude en abrir un problema en github si descubre algún error).

0

En Firefox se podría escribir:

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

se puede utilizar como:

object1 = object2.clone(); 

respuesta fue hallada h ere: source

Pero solo es magia de Firefox. Otros navegadores pueden bloquearse aquí.

+1

Eso es doble mal ... um me refiero a ... – Tom

0

Aquí hay una solución alternativa que no copia ni copia exactamente nada, pero debe dar los resultados deseados.

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

Este utiliza la función de Javascript bind para crear un objeto que pasa automáticamente en los parámetros dados a la constructor MyClass.

Tenía requisitos similares a los de OP y esto funcionó para mí, así que pensé en publicarlo, aunque me di cuenta de que algunas personas podrían necesitar una verdadera copia profunda de un objeto modificado y esto no encajaría en la factura.