2012-04-14 12 views
9

Estoy intentando clonar un objeto en Javascript. He creado mi propia "clase" que tiene funciones de prototipo.objeto clonado Javascript pierde sus funciones de prototipo

Mi problema: Cuando clono un objeto, el clon no puede acceder ni llamar a ningún prototipo de funciones.

me sale un error cuando voy a acceder a una función prototipo del clon:

clone.render no es una función

¿Me puede decir cómo puedo clonar un objeto y mantener sus funciones prototipo

Este sencillo jsFiddle demuestra el error que consigo: http://jsfiddle.net/VHEFb/1/

function cloneObject(obj) 
{ 
    // Handle the 3 simple types, and null or undefined 
    if (null == obj || "object" != typeof obj) return obj; 

    // Handle Date 
    if (obj instanceof Date) { 
    var copy = new Date(); 
    copy.setTime(obj.getTime()); 
    return copy; 
    } 

    // Handle Array 
    if (obj instanceof Array) { 
    var copy = []; 
    for (var i = 0, len = obj.length; i < len; ++i) { 
     copy[i] = cloneObject(obj[i]); 
    } 
    return copy; 
    } 

    // Handle Object 
    if (obj instanceof Object) { 
    var copy = {}; 
    for (var attr in obj) { 
     if (obj.hasOwnProperty(attr)) copy[attr] = cloneObject(obj[attr]); 
    } 
    return copy; 
    } 

    throw new Error("Unable to copy obj! Its type isn't supported."); 
} 

function MyObject(name) 
{ 
    this.name = name; 
    // I have arrays stored in this object also so a simple cloneNode(true) call wont copy those 
    // thus the need for the function cloneObject(); 
} 

MyObject.prototype.render = function() 
{ 
    alert("Render executing: "+this.name); 
} 

var base = new MyObject("base"); 
var clone = cloneObject(base); 
clone.name = "clone"; 
base.render(); 
clone.render(); // Error here: "clone.render is not a function" 
+0

que he visto [esta solución] (http://stackoverflow.com/a/728694/575527) aquí en SO . sin embargo, no hay una forma clara de clonar exactamente un objeto. [este usa jQuery] (http: // stackoverflow.com/q/122102/575527) aunque – Joseph

Respuesta

7

Algunos comentarios sobre el código:

> if (obj instanceof Date) { 
>  var copy = new Date(); 
>  copy.setTime(obj.getTime()); 

puede ser:

if (obj instanceof Date) { 
    var copy = new Date(obj); 

y

> if (obj instanceof Array) { 

devolverá falso si obj es una matriz de otro contexto global, como un iFrame. Consideremos:

 if (o && !(o.constructor.toString().indexOf("Array") == -1)) 

>  var copy = []; 
>  for (var i = 0, len = obj.length; i < len; ++i) { 
>   copy[i] = cloneObject(obj[i]); 
>  } 

Copia de los índices de una matriz a otra se puede hacer más eficiente y precisa utilizando slice:

 var copy = obj.slice(); 

, aunque se pierda cualquier otra propiedad que podrían haber sido añadidos que no son numérico. El bucle de 0 a la longitud agregará propiedades al clon que no existen en una matriz dispersa (por ejemplo, las elisiones se convertirán en miembros no definidos).

En cuanto a la parte de clonación ...

En las propiedades del objeto copiado parte, que copiará todas las propiedades, incluidas las de la cadena del original de [[Prototype]], directamente al objeto de "clon". La única forma de realmente "clonar" un objeto es establecer su [[Prototype]] en el mismo objeto que el original, luego copiar las propiedades enumerables en el original (filtrado con hasOwnProperty) al clon.

La segunda parte es trivial, la primera parte no es (en un sentido general) ya que no puede garantizar que la propiedad constructor del objeto hace referencia al objeto cuya prototype es su [[Prototype]], ni se puede garantizar que el prototipo del constructor hasn 't cambiado (es decir, es un objeto diferente) mientras tanto.

Lo más cerca que se puede obtener es utilizar Lasse Reichstein Nielsen's clone (popularizado por Douglas Crockford como beget) que hace que el objeto original del [[Prototype]] del clon, a continuación, establecer el constructor para el mismo objeto. Aunque probablemente aún necesite copiar las propiedades propias enumerables para que enmascaren las propiedades con el mismo nombre del original.

Así que realmente solo puedes clonar un objeto dentro de un contexto restringido, no puedes hacerlo en general. Y en general, esa realización conduce a un diseño en el que no es necesario clonar genéricamente los objetos.

+0

Ahora veo (mejor) su punto. Aún así, me interesarían las referencias sobre prototipos de malabares. ¿Tienes uno? – Eineki

+0

ES5 introdujo [Object.getPrototypeOf()] (https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/GetPrototypeOf) que podría usarse con 'Object.create()', pero no está bien admitido aún Tal vez con las pruebas de características para el [\ _ \ _ proto__] obsoleto (https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/Proto) podría cubrir una buena cantidad de navegadores, pero se perderá navegadores anteriores a ES5 como IE 8 y versiones anteriores y un billón de otros agentes de usuario menores en teléfonos, consolas de juegos, etc. Verifique [la tabla de compatibilidad de Kangax] (http://kangax.github.com/es5-compat-table/). – RobG

3

En lugar de

var copy = {}; 

uso

var copy = new obj.constructor; 
+0

Inicialmente sugerí su misma solución pero, en un segundo pensamiento creo que este no es el camino a seguir, ¿qué es el constructor cambia su comportamiento cada vez que se llama pensar en una clase que crea filas alternativas de una tabla con diferentes cadenas de prototipos. Sé que es un ejemplo un poco extendido, pero es una opción – Eineki

2

me instancia del objeto clon usando el constructor del objeto a ser clonada:

var copy = {}; 

habrá

var copy = new obj.constructor(); 

Es una respuesta rápida y no he reflexionado sobre los inconvenientes de tal solución (estoy pensando en el constructor pesado) pero, a primera vista, debería funcionar (no mencionaría (ni recurriría a) el esoterismo métodos como __proto__).

Actualización:

debe recurrir a object.create para resolver este problema.

var copy = {}; 

habrá

var copy = Object.create(obj.constructor.prototype); 

De esta manera, el constructor original no está llamada a crear el objeto (pensar en un constructor que hace una larga llamada AJAX para recuperar datos desde un servidor) como objeto.Crear es equivalente a

Object.create = function (proto) { 
    function F() {} 
    F.prototype = proto; 
    return new F(); 
}; 

y se puede utilizar este código si el motor JavaScript que está utilizando no soporta esta función (que se añadió a la ECMAScript 5 especificaciones)

+0

El problema principal es que 'obj.constructor' no puede hacer referencia al objeto desde el que se construyó (por ejemplo, usando' beget' de Crockford, copiado anteriormente). Solo puedes estar seguro si construiste el objeto en primer lugar, y en ese caso conoces el constructor, entonces ¿por qué no hacer referencia directamente? – RobG

+0

Ah, y el prototipo del constructor ahora puede ser un objeto diferente para que 'obj [[Prototype]]! == copie [[Prototype]]' aunque sean del mismo constructor. – RobG

+0

@RobG No entiendo el punto, quiero clonar un obj, crear uno en blanco con el prototipo deseado y copiar el prototipo original para mantener la cadena del prototipo. Hay un método para cambiar obj [[Prototype]] (creo que es un accesorio interno de solo lectura) para que difiera del obj.constructor.prototype? Yo (realmente) sabría sobre tal posibilidad – Eineki

4

Su función se puede simplificar a:

function cloneObject(obj) 
{ 
    obj = obj && obj instanceof Object ? obj : ''; 

    // Handle Date (return new Date object with old value) 
    if (obj instanceof Date) { 
    return new Date(obj); 
    } 

    // Handle Array (return a full slice of the array) 
    if (obj instanceof Array) { 
    return obj.slice(); 
    } 

    // Handle Object 
    if (obj instanceof Object) { 
    var copy = new obj.constructor(); 
    for (var attr in obj) { 
     if (obj.hasOwnProperty(attr)){ 
      if (obj[attr] instanceof Object){ 
       copy[attr] = cloneObject(obj[attr]); 
      } else { 
       copy[attr] = obj[attr]; 
      } 
     } 
    } 
    return copy; 
    } 

    throw new Error("Unable to copy obj! Its type isn't supported."); 
} 

Aquí hay una working jsfiddle

Cuestiones relacionadas