2013-04-15 12 views
14

Come clonare un'istanza di classe JavaScript?Come clonare un'istanza di classe JavaScript?

Ho provato il normale estensione jQuery, ma questo restituisce solo un oggetto vaniglia. Ho esaminato molte altre risposte sullo stack, ma non sono riuscito a trovare come clonare un'istanza.

function Parent(name) { 
    this.name = name; 
} 

Parent.prototype.sayHello = function() { 
    console.log('Hello my name is ' + this.name); 
} 

function Child(name) { 
    Parent.call(this, name); 
} 

Child.prototype = Object.create(Parent.prototype); 

var child = new Child('Billy'); 

var clone = $.extend(true, {}, child); 
clone.name = 'Bob'; 

child.sayHello(); 
clone.sayHello(); 

console.log(child instanceof Child); 
console.log(clone instanceof Child); 

http://jsfiddle.net/39gjA/

preferirei che il clone era profonda/ricorsivo. OSSIA anche tutte le proprietà che sono oggetti sono clonate.

+0

Così si vuole fare una copia esatta, ma che non crea un riferimento alla l'originale? Se vuoi oggetti multipli che non si riferiscono tra loro, perché non crei gli oggetti in un loop? – Ryan

+3

Questa risposta fornisce alcuni ragionamenti eccellenti sul perché questo sarà difficile: http://stackoverflow.com/a/728694/187954 –

risposta

10

Come clonare un'istanza di classe JavaScript?

È quasi impossibile se l'istanza è stata creata con un uso intensivo di chiusure nella funzione di costruzione. Non possiamo mai ora quali valori interni sono stati impostati e come riprodurre tale impostazione. Pertanto, il modo più semplice sarebbe se ogni classe offrisse una funzione clone che sa cosa fare.

normale jQuery si estendono solo restituisce un oggetto di vaniglia

Non necessariamente, restituisce ciò che avete passato. Utilizzare

var clone = $.extend(true, Object.create(Object.getPrototypeOf(child)), child); 

, invece, e l'uso instanceof funzionerà bene. Si noti che il true indica una copia "profonda" che può o non può essere ciò che si desidera. Inoltre, $.extend copierà felicemente anche le proprietà ereditate enumerabili, quindi potrebbe essere necessario utilizzare una più sofisticata funzione extend.

O senza jQuery a tutti, e copiando solo proprie, le proprietà enumerabili e solo utilizzando una copia:

var clone = Object.assign(Object.create(Object.getPrototypeOf(child)), child); 

Ma ancora una volta, non tutti gli oggetti sarà clonabile in questo modo, vedere il mio primo punto sopra.

-1

A cura: Quando si sa che tipo di oggetto che si desidera clonare è possibile seguire il mio esempio:

Nel tuo esempio si può semplicemente fare qualcosa di simile:

var child = new Child('Billy'); 
var clone = new Child(); 
for (var prop in child) { 
    clone[prop] = child[prop]; 
} 

Ho aggiornato il tuo jsFiddle

+0

Spiacente, il mio esempio è semplificato, e nel mio codice reale non conosco il costruttore quando Sto volendo clonare. – Petah

+0

In questo caso vorrei andare con la risposta di @ Bergi e fare var clone = $ .extend (true, Object.create (Object.getPrototypeOf (child)), child); – nmoliveira

1

Penso che non necessariamente ha bisogno di lavorare con le classi (o le istanze di funzioni), si potrebbe estendere il prototipo per applicare OOP. Il mio suggerimento è di estendere il prototipo invece di creare classi. jQuery è per DOM ma non per un trattamento avanzato degli oggetti, quindi mentre $ .extend potrebbe essere utile in alcuni casi per cose complesse ci sono librerie più avanzate.

Si potrebbe utilizzare librerie come CloneJS di lavorare facilmente con gli oggetti estensibili: https://npmjs.org/package/clonejs

Basta inserire questo script: http://quadroid.github.io/clonejs/cdn/clone.min.js

e cercare il loro esempio:

/// Forget about classes.  
// Instead of creating class (function), create prototype (object): 
var $duck = $object.clone({ 
    name: 'Unnamed', 
    quack: function(){ 
     console.log(this.name +' Duck: Quack-quack!'); 
    } 
}); 
$duck.quack();//Unnamed Duck: Quack-quack! 

/// Inheritance is simple: 
var $talkingDuck = $duck.clone({ 
    quack: function(){ 
     this.applySuper('quack'); 
     console.log('My name is '+ this.name +'!'); 
    }  
}); 

/// Forget about the `new` operator, use .create() method instead: 
var donald = $talkingDuck.create({name: 'Donald'}); 
donald.quack();// Donald Duck: Quack-quack! My name is Donald! 

/// Forget about the `instanceof` operator, use JS native 
// .isPrototypeOf() method instead: 
$duck.isPrototypeOf(donald);// true 

Inoltre penso che Backbone.js applica l'estensione del prototipo invece della creazione di classi. Usano _.extend

Alcuni più riferimenti: http://www.2ality.com/2011/11/javascript-classes.html http://underscorejs.org/#extend

1

Si dovrebbe provare qualcosa di simile:

function clone_object(o){ 
    var n=Object.create(
     Object.getPrototypeOf(o), 
     Object.getOwnPropertyNames(o).reduce(
      function(prev,cur){ 
       prev[cur]=Object.getOwnPropertyDescriptor(o,cur); 
       return prev; 
      }, 
      {} 
     ) 
    ); 
    if(!Object.isExtensible(o)){Object.preventExtensions(n);} 
    if(Object.isSealed(o)){Object.seal(n);} 
    if(Object.isFrozen(o)){Object.freeze(n);} 

    return n; 
} 

Narrative:

  1. creare il nuovo oggetto utilizzando Object.create da un prototipo e un oggetto proprietà.
  2. Per il prototipo dell'oggetto da clonare, utilizzare il prototipo dell'oggetto originale, utilizzando Object.getPrototypeOf.
  3. Per creare l'oggetto proprietà, ripetere il ciclo sulle proprietà dell'oggetto originale (utilizzando getOwnPropertyNames) e recuperare il descrittore di proprietà per ciascuna utilizzando getOwnPropertyDescriptor.
  4. Applicare le caratteristiche estensibilità/sigillate/congelate dell'oggetto originale al clone.

Questo non consente di clonare in profondità le proprietà i cui valori sono essi stessi oggetti. Questo è lasciato come esercizio al lettore ... YMMV.

0

Sarei interessato a vedere qualcuno confrontare questo metodo con altri approcci alla clonazione di istanze di classe in JavaScript che utilizzano JavaScript nativo.

// Defining a class 
 
function MyClass(args) { 
 

 
    this.foo = "bar"; 
 
} 
 
// Avoid duplicate instances of class methods in RAM by adding them to the class prototype like this 
 
MyClass.prototype.method = method; 
 
MyClass.prototype.clone = clone; 
 

 
// Define what your methods do 
 
function method() { 
 

 
    console.log(this.foo); 
 
} 
 

 
function clone() { 
 

 
    var classScope = this; 
 

 
    // Get the prototype of your class. This will create an object that has one key for every method in your class. I'm not sure if this will go up the prototype chain if you have subclasses. Someone ought to edit this answer to clarify that. 
 
    var miniMe = Object.getPrototypeOf(this); 
 

 
    // Iterate the properties of your class--all the internal key-value pairs that do get duplicated in RAM each time you instantiate the class. 
 
    Object.keys(this).forEach(iterate); 
 

 
    function iterate(key, index, list) { 
 

 
     // Add each property to your clone 
 
     miniMe[key] = classScope[key]; 
 
    } 
 
    // Return the clone 
 
    return miniMe; 
 
} 
 

 

 
// Instantiate your class 
 
var me = new MyClass(); 
 
// Clone it 
 
var miniMe = me.clone(); 
 

 
// Run some tests 
 
Object.keys(Object.getPrototypeOf(me)).forEach(iterate); 
 
Object.keys(me).forEach(iterate); 
 

 
function iterate(property, index, list) { 
 

 
    if (!miniMe.hasOwnProperty(property)) 
 
    throw new Error("miniMe does not have property " + property); 
 
} 
 

 
// Change the value of miniMe.foo and make sure it didn't impact the value of me.foo 
 
miniMe.foo = "baz"; 
 
if (me.foo === miniMe.foo) 
 
    throw new Error("me.foo should not equal miniMe.foo, because we changed its value");

3

Questo creerà una copia di un oggetto con lo stesso prototipo (classe) e le stesse proprie caratteristiche (tra cui enumerabilità/scrivibilità/getter/setter, ecc):

function clone(obj) { 
    return Object.create(
    Object.getPrototypeOf(obj), 
    Object.getOwnPropertyDescriptors(obj) 
); 
} 

(vedere here)

Non funziona necessariamente bene per gli oggetti incorporati. Ad esempio Array.isArray(clone([])) è false e clone(function() {})() dice che non è una funzione, ma per gli oggetti creati dall'utente (istanze di classe o letterali di oggetti) funziona correttamente.

Per fare un clone profonda, si dovrà ciclo sopra i descrittori di proprietà e clonare i valori in modo ricorsivo:

function deepClone(obj) { 
    if (obj === null || typeof obj !== "object") 
    return obj 
    var props = Object.getOwnPropertyDescriptors(obj) 
    for (var prop in props) { 
    props[prop].value = deepClone(props[prop].value) 
    } 
    return Object.create(
    Object.getPrototypeOf(obj), 
    props 
) 
}