2010-02-14 16 views
20

Ho provato a clonare esattamente un oggetto in javascript. So che la seguente soluzione utilizzando jQuery:Clona esattamente un oggetto in javascript

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

ma il problema con questo è, che il tipo di oggetti si perde:

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 

C'è qualche soluzione per ottenere vera sul secondo avviso?

+1

David, la differenza rispetto a tutte le altre domande sui cloni è che ho chiesto come preservare la proprietà del tipo di oggetti. – Tom

risposta

12

jQuery.extend non prevede di utilizzare l'operatore instanceof. Sta facendo una copia gloriosamente complicata, e non un vero clone. Passare attraverso gli elementi non è abbastanza. Inoltre, chiamare la funzione di costruzione non è la migliore perché perdi i tuoi argomenti. Prova questo:

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 

essere consapevoli che dal momento che il prototipo ora punta di nuovo alla originale (myObj), eventuali modifiche myObj rifletteranno in myObjClone. L'eredità prototipale di Javascript è piuttosto complicata. È necessario essere sicuri che il nuovo oggetto abbia il prototipo corretto e quindi il costruttore corretto.

Inavvertitamente, Javascript mi ​​fa male alla testa. Eppure, penso che sto leggendo questo diritto, dal ECMAScript language spec:

13.2.2 [[Construct]]
Quando il [[Construct]] metodo interno per un oggetto funzione F è chiamato con un possibilmente elenco vuoto di argomenti, sono stati eseguiti i seguenti passaggi:

  1. Lasciare che sia un oggetto nativo ECMAScript appena creato.
  2. Impostare tutti i metodi interni di obj come specificato in 8.12.
  3. Imposta la proprietà interna [[Classe]] di obj su "Oggetto".
  4. Imposta la proprietà interna [[Extensible]] di obj su true.
  5. Lasciare che proto sia il valore di chiamare la proprietà interna [[Ottieni]] di F con argomento> "prototipo".
  6. Se Type (proto) è Object, impostare la proprietà interna [[Prototype]] di obj su proto.
  7. Se Type (proto) non è Object, impostare la proprietà interna [[Prototype]] di obj sull'oggetto prototype Object incorporato standard come descritto in 15.2.4.
  8. Sia il risultato sia il risultato della chiamata della proprietà interna [[Call]] di F, fornendo> obj come valore e fornendo l'argomento list passato in [[Construct]] come argomenti.
  9. Se Type (risultato) è Object, restituisce il risultato.
  10. Oggetto di reso.

This person sembra di capire il concetto molto meglio di me. K, torno a Java ora dove nuoterò più di quanto non affondi :).

+0

Questa è una soluzione molto bella. Inoltre è possibile copiare tutte le proprietà del prototipo cloni sul clone in modo che le modifiche a myObj non si riflettano in myObjClone. Ma cosa succede se chiami di nuovo il clone su un oggetto diverso, verrà modificato il prototipo di myObjClone? – Tom

+0

Se si prova "myNewObjClone = clone (myObj)", quindi no, non si modifica il prototipo di myObjClone. L'oggetto Clone vive solo all'interno della funzione clone, quindi ne ottieni uno nuovo ogni volta che chiami clone (obj). Questo è un esempio di utilizzo della chiusura per "nascondere" una variabile, in questo caso, l'oggetto Clone. – Stephano

4

Hai considerato l'utilizzo del clone 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

Questo è un buon inizio, ma il clone fallisce se il costruttore richiede parametri: var MyClass = function (param1, param2) {alert (param1.test)}; – Tom

+0

Tom, dove ti aspetti che questa funzione 'clone' trovi gli argomenti previsti? Come si fa a saperlo? – James

+0

@ J-P - Questa è la mia domanda. C'è un modo per ottenere un clone esatto preservando le informazioni sul tipo senza sapere nulla sull'oggetto da clonare. Ora presumo sia impossibile. – Tom

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

$.extend non può copiare tutte le proprietà interne che non sono visibili (alcune sono visibili in firefox), ma se obj.constructor è corretto e non guasta senza argomenti, le proprietà interne possono essere impostate con new obj.constructor(). Se si esegue l'ereditarietà con qualcosa come Derived.prototype = new Base(), è anche necessario seguirlo con Derived.prototype.constructor = Derived per ottenere correttamente il costruttore.

Si potrebbe fare $.extend(true, new obj.constructor(), obj), ma è possibile che il costruttore crei proprietà che sono state successivamente cancellate, anche se è possibile ottenere correttamente gli argomenti del costruttore - ecco perché le proprietà devono essere eliminate prima di fare l'estensione. Non importa che gli argomenti del costruttore siano errati dato che gli effetti delle discussioni originali del costruttore - oltre a tutto ciò che è accaduto all'oggetto da allora - sono nell'oggetto che stiamo clonando.

1

Il problema è che stai passando un nuovo oggetto per copiare su "{}". Questo è il motivo per cui perdi il tuo tipo. Ho scoperto che se si avvolge l'oggetto reale prima di passarlo e di scartare successivamente l'oggetto copiato, estendere conserverà il tipo come previsto.

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

penso che questa sia la migliore risposta, grazie a te rock UOMO :) –

2

Dopo aver preso spunto da alcune risposte che ho trovato su StackOverflow, mi è venuta in mente una funzione che è abbastanza flessibile, e funziona ancora quando l'oggetto o uno dei suoi oggetti secondari ha un costruttore con parametri richiesti (grazie a Object.create). (. Grazie a Justin McCandless questo ora supporta i riferimenti ciclici così)

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

Questa funzione è parte della mia biblioteca simpleOO; tutte le correzioni di bug o miglioramenti saranno fatti lì (sentiti libero di aprire un problema su github se scopri qualche bug).

0

In Firefox si potrebbe scrivere:

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

possono essere usati come:

object1 = object2.clone(); 

risposta è stata trovata h ERE: source

Ma è solo la magia di Firefox. Altri browser potrebbero bloccarsi qui.

+1

Questo è doppio male ... um voglio dire eval ... – Tom

0

Ecco una soluzione alternativa che non copia o clona esattamente nulla, ma dovrebbe fornire i risultati desiderati.

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

Questo utilizza la funzione di Javascript bind di creare un oggetto che passa automaticamente i parametri indicati al costruttore MyClass.

avevo requisiti simili come il PO e questo ha funzionato per me, così ho pensato che avrei posto, anche se mi rendo conto che alcune persone potrebbero avere bisogno di una vera e propria copia completa su un oggetto modificato e questo non si adatta il disegno di legge.