Qual è il modo più efficiente per clonare in profondità un oggetto in JavaScript?


5180

Qual è il modo più efficiente per clonare un oggetto JavaScript? Ho visto obj = eval(uneval(o));essere utilizzato, ma non è standard e è supportato solo da Firefox .

Ho fatto cose del genere obj = JSON.parse(JSON.stringify(o));ma metto in dubbio l'efficienza.

Ho anche visto funzioni di copia ricorsiva con vari difetti.
Sono sorpreso che non esista una soluzione canonica.


566
Eval non è cattivo. Usare eval male è. Se hai paura dei suoi effetti collaterali, lo stai usando in modo sbagliato. Gli effetti collaterali che temi sono i motivi per usarlo. Qualcuno ha effettivamente risposto alla tua domanda?
James,

15
La clonazione di oggetti è un'attività complessa, in particolare con oggetti personalizzati di raccolte arbitrarie. Quale probabilmente perché non esiste un modo pronto per farlo.
b01,

12
eval()è generalmente una cattiva idea perché molti ottimizzatori del motore Javascript devono disattivarsi quando si tratta di variabili impostate tramiteeval . Il solo fatto eval()di avere nel tuo codice può portare a prestazioni peggiori.
user56reinstatemonica8


12
Si noti che il JSONmetodo perderà tutti i tipi di Javascript che non hanno equivalenti in JSON. Ad esempio: JSON.parse(JSON.stringify({a:null,b:NaN,c:Infinity,d:undefined,e:function(){},f:Number,g:false}))genererà{a: null, b: null, c: null, g: false}
oriadam

Risposte:


4731

Clonazione profonda nativa

Si chiama "clonazione strutturata", funziona sperimentalmente nel Nodo 11 e versioni successive e si spera che finisca nei browser. Vedi questa risposta per maggiori dettagli.

Clonazione rapida con perdita di dati - JSON.parse / stringify

Se non si utilizza Dates, funzioni, undefined, Infinity, espressioni regolari, Mappe, Set, Blobs, filelists, ImageDatas, array sparse, tipizzati array o altri tipi complessi all'interno del vostro oggetto, molto semplice rivestimento profonda clone di un oggetto è:

JSON.parse(JSON.stringify(object))

const a = {
  string: 'string',
  number: 123,
  bool: false,
  nul: null,
  date: new Date(),  // stringified
  undef: undefined,  // lost
  inf: Infinity,  // forced to 'null'
  re: /.*/,  // lost
}
console.log(a);
console.log(typeof a.date);  // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date);  // result of .toISOString()

Vedere la risposta di Corban per i benchmark.

Clonazione affidabile tramite una libreria

Poiché la clonazione di oggetti non è banale (tipi complessi, riferimenti circolari, funzioni ecc.), La maggior parte delle librerie principali fornisce funzioni per clonare oggetti. Non reinventare la ruota : se stai già utilizzando una libreria, controlla se ha una funzione di clonazione di oggetti. Per esempio,

ES6

Per completezza, si noti che ES6 offre due meccanismi di copia superficiale: Object.assign()e la sintassi di diffusione . che copia i valori di tutte le proprietà proprie enumerabili da un oggetto a un altro. Per esempio:

var A1 = {a: "2"};
var A2 = Object.assign({}, A1);
var A3 = {...A1};  // Spread Syntax

7
@ThiefMaster github.com/jquery/jquery/blob/master/src/core.js alla riga 276 (c'è un po 'di codice che fa qualcos'altro ma il codice per "come farlo in JS" è lì :)
Rune FS

7
Ecco il codice JS dietro la copia profonda di jQuery, per chiunque sia interessato: github.com/jquery/jquery/blob/master/src/core.js#L265-327
Alex W

194
Woah! Giusto per essere super-chiari: non ho idea del perché questa risposta sia stata scelta come la risposta giusta, questa era una risposta alle risposte fornite di seguito: stackoverflow.com/a/122190/6524 (che stava raccomandando .clone(), che non è il codice giusto per essere utilizzando in questo contesto). Purtroppo questa domanda ha attraversato così tante revisioni che la discussione originale non è più nemmeno evidente! Segui i consigli di Corban e scrivi un ciclo o copia le proprietà direttamente su un nuovo oggetto, se ti interessa la velocità. O provalo tu stesso!
John Resig,

9
Questa è una domanda JavaScript (nessuna menzione di jQuery).
gphilip,

60
Come si farebbe questo senza usare jQuery?
Awesomeness01

2266

Dai un'occhiata a questo benchmark: http://jsben.ch/#/bWfk9

Nei miei precedenti test in cui la velocità era una delle principali preoccupazioni che ho riscontrato

JSON.parse(JSON.stringify(obj))

essere il modo più lento per clonare in profondità un oggetto (è più lento di jQuery.extend condeep flag impostato su true del 10-20%).

jQuery.extend è piuttosto veloce quando la deepbandiera è impostata sufalse (clone superficiale). È una buona opzione, perché include una logica aggiuntiva per la convalida del tipo e non copia su proprietà indefinite, ecc., Ma ciò rallenterà anche un po '.

Se conosci la struttura degli oggetti che stai tentando di clonare o puoi evitare array nidificati profondi, puoi scrivere un semplice for (var i in obj) ciclo per clonare il tuo oggetto mentre controlli hasOwnProperty e sarà molto più veloce di jQuery.

Infine, se si sta tentando di clonare una struttura di oggetto nota in un hot loop, è possibile ottenere MOLTE ALTRE PRESTAZIONI semplicemente incorporando la procedura di clonazione e costruendo manualmente l'oggetto.

I motori di tracciamento JavaScript fanno schifo per l'ottimizzazione dei for..inloop e il controllo di hasOwnProperty rallenta anche te. Clone manuale quando la velocità è un must assoluto.

var clonedObject = {
  knownProp: obj.knownProp,
  ..
}

Fai attenzione utilizzando il JSON.parse(JSON.stringify(obj))metodo sugli Dateoggetti: JSON.stringify(new Date())restituisce una rappresentazione in formato stringa della data in formato ISO, che JSON.parse() non viene riconvertita in un Dateoggetto. Vedi questa risposta per maggiori dettagli .

Inoltre, tieni presente che, almeno in Chrome 65, la clonazione nativa non è la strada da percorrere. Secondo JSPerf, eseguire la clonazione nativa creando una nuova funzione è quasi 800 volte più lento rispetto all'utilizzo di JSON.stringify che è incredibilmente veloce su tutta la linea.

Aggiornamento per ES6

Se si utilizza Javascript ES6, provare questo metodo nativo per la clonazione o la copia superficiale.

Object.assign({}, obj);

4
@trysis Object.create non sta clonando l'oggetto, sta usando l'oggetto prototipo ... jsfiddle.net/rahpuser/yufzc1jt/2
rahpuser

105
Questo metodo rimuoverà anche il keystuo object, che ha functionscome valori, perché JSONnon supporta le funzioni.
Karlen Kishmiryan,

39
Inoltre, tieni presente che l'utilizzo di JSON.parse(JSON.stringify(obj))Date Objects converte anche la data in UTC nella rappresentazione di stringa nel formato ISO8601 .
dnlgmzddr,

31
L'approccio JSON soffoca anche su riferimenti circolari.
rich remer

28
@velop, Object.assign ({}, objToClone) sembra però fare un clone superficiale - usandolo mentre si gioca nella console degli strumenti di sviluppo, il clone oggetto puntava ancora a un riferimento all'oggetto clonato. Quindi non penso che sia davvero applicabile qui.
Garrett Simpson,

473

Supponendo che tu abbia solo variabili e non funzioni nel tuo oggetto, puoi semplicemente usare:

var newObject = JSON.parse(JSON.stringify(oldObject));

86
lo svantaggio di questo approccio, come ho appena scoperto, è se il tuo oggetto ha delle funzioni (il mio ha getter e setter interni), quindi questi vengono persi quando vengono sottoposti a stringhe. Se questo è tutto ciò di cui hai bisogno, questo metodo va bene ..
Markive

31
@Jason, Il motivo per cui questo metodo è più lento della copia superficiale (su un oggetto profondo) è che questo metodo, per definizione, copia profonda. Ma poiché JSONè implementato nel codice nativo (nella maggior parte dei browser), questo sarà notevolmente più veloce rispetto all'uso di qualsiasi altra soluzione di copia profonda basata su javascript e talvolta potrebbe essere più veloce di una tecnica di copia superficiale basata su javascript (vedere: jsperf.com/cloning -an-object / 79 ).
MiJyn

35
JSON.stringify({key: undefined}) //=> "{}"
Web_Designer,

32
questa tecnica distruggerà anche tutti gli Dateoggetti che sono memorizzati all'interno dell'oggetto, convertendoli in forma di stringa.
fstab,

13
Non riuscirà a copiare tutto ciò che non fa parte delle specifiche JSON ( json.org )
cdmckay

397

Clonazione strutturata

Lo standard HTML include un algoritmo di clonazione / serializzazione strutturato interno che può creare cloni profondi di oggetti. È ancora limitato a determinati tipi predefiniti, ma oltre ai pochi tipi supportati da JSON supporta anche Date, RegExps, Mappe, Set, BLOB, FileList, ImageDatas, Array sparsi, Array tipizzati e probabilmente altri in futuro . Conserva anche i riferimenti all'interno dei dati clonati, consentendogli di supportare strutture cicliche e ricorsive che potrebbero causare errori per JSON.

Supporto in Node.js: sperimentale 🙂

Il v8modulo in Node.js attualmente (a partire dal Nodo 11) espone direttamente l'API di serializzazione strutturata , ma questa funzionalità è ancora contrassegnata come "sperimentale" e soggetta a modifiche o rimozione nelle versioni future. Se stai utilizzando una versione compatibile, la clonazione di un oggetto è semplice come:

const v8 = require('v8');

const structuredClone = obj => {
  return v8.deserialize(v8.serialize(obj));
};

Supporto diretto nei browser: forse alla fine? 😐

I browser attualmente non forniscono un'interfaccia diretta per l'algoritmo di clonazione strutturato, ma una structuredClone()funzione globale è stata discussa in whatwg / html # 793 su GitHub . Come attualmente proposto, usarlo per la maggior parte degli scopi sarebbe semplice come:

const clone = structuredClone(original);

A meno che non vengano spediti, le implementazioni del clone strutturato dei browser sono esposte solo indirettamente.

Soluzione alternativa asincrona: utilizzabile. 😕

Il modo più basso per creare un clone strutturato con le API esistenti consiste nel pubblicare i dati attraverso una porta di un MessageChannels . L'altra porta emetterà un messageevento con un clone strutturato dell'allegato .data. Sfortunatamente, l'ascolto di questi eventi è necessariamente asincrono e le alternative sincrone sono meno pratiche.

class StructuredCloner {
  constructor() {
    this.pendingClones_ = new Map();
    this.nextKey_ = 0;

    const channel = new MessageChannel();
    this.inPort_ = channel.port1;
    this.outPort_ = channel.port2;

    this.outPort_.onmessage = ({data: {key, value}}) => {
      const resolve = this.pendingClones_.get(key);
      resolve(value);
      this.pendingClones_.delete(key);
    };
    this.outPort_.start();
  }

  cloneAsync(value) {
    return new Promise(resolve => {
      const key = this.nextKey_++;
      this.pendingClones_.set(key, resolve);
      this.inPort_.postMessage({key, value});
    });
  }
}

const structuredCloneAsync = window.structuredCloneAsync =
    StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);

Esempio di utilizzo:

const main = async () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = await structuredCloneAsync(original);

  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));

  console.log("Assertions complete.");
};

main();

Soluzioni alternative sincrone: Terribile! 🤢

Non ci sono buone opzioni per la creazione di cloni strutturati in modo sincrono. Ecco un paio di hack poco pratici.

history.pushState()ed history.replaceState()entrambi creano un clone strutturato del loro primo argomento e assegnano quel valore a history.state. Puoi usarlo per creare un clone strutturato di qualsiasi oggetto come questo:

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

Esempio di utilizzo:

Sebbene sincrono, questo può essere estremamente lento. Presenta tutto il sovraccarico associato alla manipolazione della cronologia del browser. La chiamata ripetuta di questo metodo può causare la mancata risposta temporanea di Chrome.

Il Notificationcostruttore crea un clone strutturato dei suoi dati associati. Tenta anche di mostrare all'utente una notifica del browser, ma ciò fallirà silenziosamente a meno che tu non abbia richiesto il permesso di notifica. Nel caso in cui tu abbia l'autorizzazione per altri scopi, chiuderemo immediatamente la notifica che abbiamo creato.

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.onshow = n.close.bind(n);
  return n.data;
};

Esempio di utilizzo:


3
@rynah Ho appena esaminato di nuovo le specifiche e hai ragione: i metodi history.pushState()e history.replaceState()sono entrambi impostati in modo sincrono history.statesu un clone strutturato del loro primo argomento. Un po 'strano, ma funziona. Sto aggiornando la mia risposta ora.
Jeremy Banks,

40
Questo è così sbagliato! L'API non deve essere utilizzata in questo modo.
Fardin K.,

209
Come il ragazzo che ha implementato pushState in Firefox, provo uno strano mix di orgoglio e repulsione per questo hack. Ben fatto ragazzi.
Justin L.

L'hacking pushState o di notifica non funziona per alcuni tipi di oggetti come Funzione
Shishir Arora,

323

Se non ce n'era uno incorporato, potresti provare:

function clone(obj) {
    if (obj === null || typeof (obj) !== 'object' || 'isActiveClone' in obj)
        return obj;

    if (obj instanceof Date)
        var temp = new obj.constructor(); //or new Date(obj);
    else
        var temp = obj.constructor();

    for (var key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            obj['isActiveClone'] = null;
            temp[key] = clone(obj[key]);
            delete obj['isActiveClone'];
        }
    }
    return temp;
}

20
La soluzione JQuery funzionerà con elementi DOM ma non con qualsiasi oggetto. Mootools ha lo stesso limite. Vorrei che avessero un "clone" generico per qualsiasi oggetto ... La soluzione ricorsiva dovrebbe funzionare per qualsiasi cosa. È probabilmente la strada da percorrere.
jschrab,

5
Questa funzione si interrompe se l'oggetto da clonare ha un costruttore che richiede parametri. Sembra che possiamo cambiarlo in "var temp = new Object ()" e farlo funzionare in ogni caso, no?
Andrew Arnott,

3
Andrew, se lo cambi in var temp = new Object (), il tuo clone non avrà lo stesso prototipo dell'oggetto originale. Prova a usare: 'var newProto = function () {}; newProto.prototype = obj.constructor; var temp = new newProto (); '
limscoder

1
Simile alla risposta del limscoder, vedi la mia risposta di seguito su come eseguire questa operazione senza chiamare il costruttore: stackoverflow.com/a/13333781/560114
Matt Browne

3
Per gli oggetti che contengono riferimenti a parti secondarie (ovvero reti di oggetti), ciò non funziona: se due riferimenti puntano allo stesso oggetto secondario, la copia ne contiene due copie diverse. E se ci sono riferimenti ricorsivi, la funzione non terminerà mai (beh, almeno non nel modo desiderato :-) Per questi casi generali, devi aggiungere un dizionario di oggetti già copiati e controllare se l'hai già copiato ... La programmazione è complessa quando si utilizza un linguaggio semplice
virtualnobi l'

153

Il modo efficiente per clonare (non clonare in profondità) un oggetto in una riga di codice

Un Object.assignmetodo fa parte dello standard ECMAScript 2015 (ES6) e fa esattamente ciò di cui hai bisogno.

var clone = Object.assign({}, obj);

Il metodo Object.assign () viene utilizzato per copiare i valori di tutte le proprietà proprie enumerabili da uno o più oggetti di origine in un oggetto di destinazione.

Leggi di più...

Il polyfill per supportare i browser più vecchi:

if (!Object.assign) {
  Object.defineProperty(Object, 'assign', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: function(target) {
      'use strict';
      if (target === undefined || target === null) {
        throw new TypeError('Cannot convert first argument to object');
      }

      var to = Object(target);
      for (var i = 1; i < arguments.length; i++) {
        var nextSource = arguments[i];
        if (nextSource === undefined || nextSource === null) {
          continue;
        }
        nextSource = Object(nextSource);

        var keysArray = Object.keys(nextSource);
        for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
          var nextKey = keysArray[nextIndex];
          var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
          if (desc !== undefined && desc.enumerable) {
            to[nextKey] = nextSource[nextKey];
          }
        }
      }
      return to;
    }
  });
}

82
Questo non viene copiato in modo ricorsivo, quindi non offre davvero una soluzione al problema della clonazione di un oggetto.
mwhite

5
Questo metodo ha funzionato, anche se ne ho testati alcuni e _.extend ({}, (obj)) è stato di gran lunga il più veloce: 20 volte più veloce di JSON.parse e il 60% più veloce di Object.assign, per esempio. Copia abbastanza bene tutti gli oggetti secondari.
Nico,

11
@mwhite c'è una differenza tra clone e deep-clone. Questa risposta infatti clona, ​​ma non clona in profondità.
Meirion Hughes,

57
l'op ha chiesto un clone profondo. questo non fa clone profondo.
user566245

9
Questo metodo crea una copia SHALLOW e non una copia DEEP ! Per questo motivo è una risposta completamente sbagliata !
Bharata,

97

Codice:

// extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returned
function extend(from, to)
{
    if (from == null || typeof from != "object") return from;
    if (from.constructor != Object && from.constructor != Array) return from;
    if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function ||
        from.constructor == String || from.constructor == Number || from.constructor == Boolean)
        return new from.constructor(from);

    to = to || new from.constructor();

    for (var name in from)
    {
        to[name] = typeof to[name] == "undefined" ? extend(from[name], null) : to[name];
    }

    return to;
}

Test:

var obj =
{
    date: new Date(),
    func: function(q) { return 1 + q; },
    num: 123,
    text: "asdasd",
    array: [1, "asd"],
    regex: new RegExp(/aaa/i),
    subobj:
    {
        num: 234,
        text: "asdsaD"
    }
}

var clone = extend(obj);

3
che dire var obj = {}eobj.a = obj
neaumusica del

5
Non capisco questa funzione. Supponiamo che from.constructorsia Dateper esempio. Come verrebbe ifraggiunto il terzo test quando il secondo iftest avrebbe avuto esito positivo e avrebbe restituito la funzione (da allora Date != Object && Date != Array)?
Adam McKee,

1
@AdamMcKee Perché passare l'argomento javascript e l'assegnazione delle variabili è complicato . Questo approccio funziona alla grande, comprese le date (che in effetti sono gestite dal secondo test) - violino per testare qui: jsfiddle.net/zqv9q9c6 .
brichins,

1
@NickSweeting: prova - potrebbe essere che funzioni. In caso contrario, correggilo e aggiorna la risposta. Funziona così in comunità :)
Kamarey,

1
Questa funzione non clona la regex nel test, la condizione "from.constructor! = Object && from.constructor! = Array" restituisce sempre true per altri costruttori come Numero, Data e così via.
aMarCruz,

95

Questo è quello che sto usando:

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(typeof(obj[i])=="object" && obj[i] != null)
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

8
Questo non sembra giusto. cloneObject({ name: null })=>{"name":{}}
Niyaz,

13
Ciò è dovuto a un'altra cosa stupida in javascript typeof null > "object"ma Object.keys(null) > TypeError: Requested keys of a value that is not an object.cambia la condizione inif(typeof(obj[i])=="object" && obj[i]!=null)
Vitim.us

Ciò assegnerà le proprietà enumerabili ereditate di obj direttamente al clone e presuppone che obj sia un semplice oggetto.
RobG

Ciò crea anche errori negli array, che vengono convertiti in oggetti con tasti numerici.
lama

Non è un problema se non usi null.
Jorge Bucaran,

78

Copia approfondita per performance: classificata dalla migliore alla peggiore

  • Riassegnazione "=" (array di stringhe, array di numeri - solo)
  • Slice (array di stringhe, array di numeri - solo)
  • Concatenazione (array di stringhe, array di numeri - solo)
  • Funzione personalizzata: copia for-loop o ricorsiva
  • $ .extend di jQuery
  • JSON.parse (array di stringhe, array di numeri, array di oggetti - solo)
  • _.Clone di Underscore.js (array di stringhe, array di numeri - solo)
  • _.CloneDeep di Lo-Dash

Copia profonda di una matrice di stringhe o numeri (un livello - nessun puntatore di riferimento):

Quando un array contiene numeri e stringhe - funzioni come .slice (), .concat (), .splice (), l'operatore di assegnazione "=" e la funzione clone di Underscore.js; farà una copia profonda degli elementi dell'array.

Dove la riassegnazione ha le prestazioni più veloci:

var arr1 = ['a', 'b', 'c'];
var arr2 = arr1;
arr1 = ['a', 'b', 'c'];

E .slice () ha prestazioni migliori di .concat (), http://jsperf.com/duplicate-array-slice-vs-concat/3

var arr1 = ['a', 'b', 'c'];  // Becomes arr1 = ['a', 'b', 'c']
var arr2a = arr1.slice(0);   // Becomes arr2a = ['a', 'b', 'c'] - deep copy
var arr2b = arr1.concat();   // Becomes arr2b = ['a', 'b', 'c'] - deep copy

Copia profonda di una matrice di oggetti (due o più livelli - puntatori di riferimento):

var arr1 = [{object:'a'}, {object:'b'}];

Scrivi una funzione personalizzata (ha prestazioni più veloci di $ .extend () o JSON.parse):

function copy(o) {
   var out, v, key;
   out = Array.isArray(o) ? [] : {};
   for (key in o) {
       v = o[key];
       out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
   }
   return out;
}

copy(arr1);

Utilizzare le funzioni di utilità di terze parti:

$.extend(true, [], arr1); // Jquery Extend
JSON.parse(arr1);
_.cloneDeep(arr1); // Lo-dash

Dove $ .extend di jQuery ha prestazioni migliori:


Ne ho testati alcuni e _.extend ({}, (obj)) è stato di gran lunga il più veloce: 20 volte più veloce di JSON.parse e il 60% più veloce di Object.assign, per esempio. Copia abbastanza bene tutti gli oggetti secondari.
Nico,

4
Tutti i tuoi esempi sono superficiali, un livello. Questa non è una buona risposta La domanda riguardava la clonazione profonda, vale a dire almeno due livelli.
Karl Morrison,

1
Una copia profonda è quando un oggetto viene copiato nella sua interezza senza l'uso di puntatori di riferimento su altri oggetti. Le tecniche nella sezione "Copia profonda di una matrice di oggetti", come jQuery.extend () e la funzione personalizzata (che è ricorsiva) copiano gli oggetti con "almeno due livelli". Quindi, no, non tutti gli esempi sono copie "di un livello".
tfmontague,

1
Mi piace la tua funzione di copia personalizzata, ma dovresti escludere valori null, altrimenti tutti i valori null vengono convertiti in oggetti, ad esempio:out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
josi,

2
@HossamMourad - Il bug è stato corretto da Josi il 1 ° febbraio (nel commento sopra) e non sono riuscito ad aggiornare correttamente la risposta. Ci dispiace che questo errore abbia provocato un refactor della tua base di codice.
tfmontague,

64
var clone = function() {
    var newObj = (this instanceof Array) ? [] : {};
    for (var i in this) {
        if (this[i] && typeof this[i] == "object") {
            newObj[i] = this[i].clone();
        }
        else
        {
            newObj[i] = this[i];
        }
    }
    return newObj;
}; 

Object.defineProperty( Object.prototype, "clone", {value: clone, enumerable: false});

Buona risposta ma questo non riesce per i riferimenti circolari.
Luca,

59

Copia di oggetti in profondità in JavaScript (penso che il migliore e il più semplice)

1. Utilizzo di JSON.parse (JSON.stringify (oggetto));

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = JSON.parse(JSON.stringify(obj));
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

2.Uso del metodo creato

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(obj[i] != null &&  typeof(obj[i])=="object")
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = cloneObject(obj);
obj.b.c = 20;

console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

3. Utilizzo del lodash di link _.cloneDeep di Lo-Dash

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

4. Utilizzo del metodo Object.assign ()

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

MA SBAGLIATO QUANDO

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = Object.assign({}, obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// Note: Properties on the prototype chain and non-enumerable properties cannot be copied.

5.Utilizzo di Underscore.js _.clone link Underscore.js

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

MA SBAGLIATO QUANDO

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// (Create a shallow-copied clone of the provided plain object. Any nested objects or arrays will be copied by reference, not duplicated.)

JSBEN.CH Performance Benchmarking Playground 1 ~ 3 http://jsben.ch/KVQLd Prestazioni Copia di oggetti in profondità in JavaScript


5
Object.assign()non esegue una copia approfondita
Roymunson il

1
dovresti aggiungere benchmark per questi; sarebbe molto utile
jcollum,

quando ho usato il "metodo creato" su un oggetto contenente un array non ero in grado di usare pop () o splice () su di esso, non capisco perché? let data = {title:["one", "two"]}; let tmp = cloneObject(data); tmp.title.pop();getta: TypeError: tmp.title.pop is not a function(ovviamente pop () funziona bene se solo do let tmp = data; ma poi non posso modificare tmp senza influire sui dati)
hugogogo

Ehi, il tuo ultimo esempio è sbagliato. Secondo me, devi usare _clone e non _cloneDeep per l'esempio sbagliato.
Kenanyildiz,

Questo metodo creato (2.) non funzionerà per gli array, vero?
Toivo Säwén,

57

C'è una libreria (chiamata "clone") , che lo fa abbastanza bene. Fornisce la clonazione / copia ricorsiva più completa di oggetti arbitrari che io conosca. Supporta anche riferimenti circolari, che non sono ancora coperti dalle altre risposte.

Puoi trovarlo anche su npm . Può essere utilizzato per il browser e Node.js.

Ecco un esempio su come usarlo:

Installalo con

npm install clone

o impacchettalo con Ender .

ender build clone [...]

Puoi anche scaricare manualmente il codice sorgente.

Quindi puoi usarlo nel tuo codice sorgente.

var clone = require('clone');

var a = { foo: { bar: 'baz' } };  // inital value of a
var b = clone(a);                 // clone a -> b
a.foo.bar = 'foo';                // change a

console.log(a);                   // { foo: { bar: 'foo' } }
console.log(b);                   // { foo: { bar: 'baz' } }

(Dichiarazione di non responsabilità: sono l'autore della biblioteca.)


3
Il clone di npm è stato prezioso per me per la clonazione di oggetti nidificati arbitrariamente. Questa è la risposta esatta.
Andy Ray,

qual è la performance della tua lib rispetto a diciamo JSON.parse(JSON.stringify(obj))?
becco

Ecco una libreria che afferma che ci sono opzioni più veloci. Non ho testato però.
pvorb,

Buona soluzione e questo supporta riferimenti circolari (a differenza dell'analisi JSON)
Luca

55

Cloning un oggetto è sempre stato un problema in JS, ma era tutto prima di ES6, elenchiamo diversi modi di copiare un oggetto in JavaScript di seguito, immagino di avere l'oggetto sotto e vorrei avere una copia approfondita di quello:

var obj = {a:1, b:2, c:3, d:4};

Esistono alcuni modi per copiare questo oggetto, senza cambiare l'origine:

1) ES5 +, Utilizzando una semplice funzione per fare la copia per te:

function deepCopyObj(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = cloneSO(obj[i]);
        }
        return copy;
    }
    if (obj instanceof Object) {
        var copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = cloneSO(obj[attr]);
        }
        return copy;
    }
    throw new Error("Unable to copy obj this object.");
}

2) ES5 +, utilizzando JSON.parse e JSON.stringify.

var  deepCopyObj = JSON.parse(JSON.stringify(obj));

3) AngularJs:

var  deepCopyObj = angular.copy(obj);

4) jQuery:

var deepCopyObj = jQuery.extend(true, {}, obj);

5) UnderscoreJs & Loadash:

var deepCopyObj = _.cloneDeep(obj); //latest version UndescoreJs makes shallow copy

Spero che questi aiuti ...


2
clone in underscore non è un clone profondo nella versione corrente
Rogelio

Grazie. yes come nuovo documento per Underscore ... clone_.clone (oggetto) Crea un clone con copia superficiale dell'oggetto normale fornito. Eventuali oggetti o matrici nidificati verranno copiati per riferimento, non duplicati. _.clone ({name: 'moe'}); => {nome: 'moe'};
Alireza,

59
Object.assignnon senza profonda copiare. Esempio: var x = { a: { b: "c" } }; var y = Object.assign({}, x); x.a.b = "d". Se questa fosse una copia profonda, lo y.a.bsarebbe ancora c, ma lo è ora d.
kba,

8
Object.assign () clona solo il primo livello di proprietà!
Avere il

5
che cos'è la funzione cloneSO ()?
pastorello,

53

So che questo è un vecchio post, ma ho pensato che questo potesse essere di qualche aiuto per la prossima persona che inciampa.

Finché non si assegna un oggetto a nulla, non mantiene alcun riferimento in memoria. Quindi, per creare un oggetto che desideri condividere tra altri oggetti, dovrai creare una fabbrica in questo modo:

var a = function(){
    return {
        father:'zacharias'
    };
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);

16
Questa risposta non è realmente rilevante perché la domanda è: data l'istanza b come si crea una copia c MENTRE non si conosce factory a o non si desidera utilizzare factory a. Il motivo per cui si potrebbe non voler utilizzare la fabbrica è che dopo l'istanza b potrebbe essere stato inizializzato con dati aggiuntivi (ad es. Input dell'utente).
Noel Abrahams,

12
È vero che questa non è davvero una risposta alla domanda, ma penso sia importante che sia qui perché è la risposta alla domanda che sospetto che molte delle persone che vengono qui abbiano davvero intenzione di porre.
Punto

8
Scusate ragazzi, non capisco davvero perché così tanti voti. La clonazione di un oggetto è un concetto abbastanza chiaro, si coniuga un oggetto da UN ALTRO oggetto e non ha molto a che fare con la creazione di uno nuovo con il modello di fabbrica.
apre il

2
Mentre questo funziona con oggetti predefiniti, la "clonazione" in questo modo non riconoscerà le nuove proprietà aggiunte all'oggetto originale. Se si crea a, aggiungere una nuova proprietà a a, quindi creare b. b non avrà la nuova proprietà. In sostanza il modello di fabbrica è immutabile a nuove proprietà. Non si tratta di clonazione paradigmatica. Vedi: jsfiddle.net/jzumbrun/42xejnbx
Jon

1
Penso che questo sia un buon consiglio, in generale, poiché invece di utilizzare const defaultFoo = { a: { b: 123 } };puoi andare const defaultFoo = () => ({ a: { b: 123 } };e il tuo problema è risolto. Tuttavia, in realtà non è una risposta alla domanda. Potrebbe aver avuto più senso un commento sulla domanda, non una risposta completa.
Josh da Qaribou,

48

Se lo stai usando, la libreria Underscore.js ha un metodo clone .

var newObject = _.clone(oldObject);

24
lodash ha un metodo cloneDeep, supporta anche un altro parametro da clonare per approfondire: lodash.com/docs#clone e lodash.com/docs#cloneDeep
opensas

12
@opensas concordato. Lodash è generalmente superiore al carattere di sottolineatura
nha

7
Suggerisco di eliminare questa e tutte le altre risposte che sono solo riferimenti di una riga al .clone(...)metodo di una libreria di utilità . Tutte le principali biblioteche li avranno e le brevi e ripetute risposte non dettagliate non sono utili alla maggior parte dei visitatori, che non useranno quella particolare biblioteca.
Jeremy Banks,

41

Ecco una versione della risposta di ConroyP sopra che funziona anche se il costruttore ha richiesto parametri:

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

function deepCopy(obj) {
    if(obj == null || typeof(obj) !== 'object'){
        return obj;
    }
    //make sure the returned object has the same prototype as the original
    var ret = object_create(obj.constructor.prototype);
    for(var key in obj){
        ret[key] = deepCopy(obj[key]);
    }
    return ret;
}

Questa funzione è disponibile anche nella mia libreria simpleoo .

Modificare:

Ecco una versione più robusta (grazie a Justin McCandless ora supporta anche riferimenti ciclici):

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

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

30

Di seguito vengono create due istanze dello stesso oggetto. L'ho trovato e lo sto usando attualmente. È semplice e facile da usare.

var objToCreate = JSON.parse(JSON.stringify(cloneThis));

C'è qualcosa di sbagliato in questa risposta? È più utile come soluzione autonoma, ma semplice; ma la soluzione jQuery è più popolare. Perché?
ceremcem,

Sì per favore fammi sapere. Sembra funzionare come previsto, se c'è qualche rottura nascosta da qualche parte, devo usare una soluzione diversa.
Nathan Roger

4
Per un oggetto semplice, questo è circa 6 volte più lento in Chrome rispetto alla risposta data e diventa molto più lento con l'aumentare della complessità dell'oggetto. Si ridimensiona terribilmente e può strozzare l'applicazione molto rapidamente.
tic

1
Non hai bisogno di dati, solo una comprensione di ciò che sta succedendo. Questa tecnica di clonazione serializza l'intero oggetto in una stringa, quindi analizza la serializzazione di tale stringa per creare un oggetto. Di conseguenza, sarà molto più lento della semplice riorganizzazione della memoria (che è ciò che fanno i cloni più sofisticati). Detto questo, per i progetti di piccole e medie dimensioni (a seconda della definizione di "media"), a chi importa se è addirittura 1000 volte meno efficiente? Se i tuoi oggetti sono piccoli e non li cloni, una tonnellata di 1000x praticamente di nulla è praticamente nulla.
machineghost,

3
Inoltre, questo metodo perde i metodi (o qualsiasi altra cosa non consentita in JSON), più - JSON.stringify convertirà gli oggetti Date in stringhe, ... e non viceversa;) Evita questa soluzione.
Mr MT

22

Crockford suggerisce (e preferisco) di usare questa funzione:

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

var newObject = object(oldObject);

È conciso, funziona come previsto e non hai bisogno di una biblioteca.


MODIFICARE:

Questo è un polyfill per Object.create, quindi puoi anche usarlo.

var newObject = Object.create(oldObject);

NOTA: se si utilizza parte di questo, è possibile che si verifichino problemi con alcune iterazioni che lo utilizzano hasOwnProperty. Perché, createcreare un nuovo oggetto vuoto che ereditaoldObject . Ma è ancora utile e pratico per la clonazione di oggetti.

Ad esempio se oldObject.a = 5;

newObject.a; // is 5

ma:

oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false

9
correggimi se sbaglio, ma non è la funzione generatrice di Crockford per l'eredità prototipale? Come si applica al clone?
Alex Nolasco,

3
Sì, avevo paura di questa discussione: qual è la differenza pratica tra clone, copia ed eredità prototipale, quando dovresti usare ciascuna e quali funzioni in questa pagina stanno effettivamente facendo cosa? Ho trovato questa pagina SO cercando su Google "oggetto copia javascript". Quello che stavo davvero cercando era la funzione sopra, quindi sono tornato a condividere. La mia ipotesi è che anche il ricercatore lo stesse cercando.
Chris Broski,

51
La differenza tra clone / copia ed eredità è che, usando il tuo esempio, quando cambio una proprietà di oldObject, la proprietà viene cambiata anche in newObject. Se fai una copia, puoi fare ciò che vuoi con oldObject senza cambiare newObject.
Ridcully,

13
Questo interromperà il controllo hasOwnProperty, quindi è un modo piuttosto confuso di clonare un oggetto e ti darà risultati inaspettati.
Corban Brook,

var extendObj = function(childObj, parentObj) { var tmpObj = function () {} tmpObj.prototype = parentObj.prototype; childObj.prototype = new tmpObj(); childObj.prototype.constructor = childObj; };... davidshariff.com/blog/javascript-inheritance-patterns
Cody

22

Lodash ha un buon metodo _.cloneDeep (value) :

var objects = [{ 'a': 1 }, { 'b': 2 }];

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false

5
Suggerisco di eliminare questa e tutte le altre risposte che sono solo riferimenti di una riga al .clone(...)metodo di una libreria di utilità . Tutte le principali biblioteche li avranno e le brevi e ripetute risposte non dettagliate non sono utili alla maggior parte dei visitatori, che non useranno quella particolare biblioteca.
Jeremy Banks,

Un modo più semplice è usare _.merge({}, objA). Se solo lodash non mutasse gli oggetti in primo luogo, la clonefunzione non sarebbe necessaria.
Rebs,

7
Le ricerche di Google per la clonazione di oggetti JS fanno riferimento qui. Sto usando Lodash quindi questa risposta è rilevante per me. Non andare tutto "cancellatore wikipedia" sulle risposte per favore.
Rebs,

2
Nel Nodo 9, JSON.parse (JSON.stringify (arrayOfAbout5KFlatObjects) è molto più veloce di _.deepClone (arrayOfAbout5KFlatObjects).
Dan Dascalescu,

21
function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }

17
Il problema con il metodo è che se hai oggetti secondari all'interno dell'oggetto, i loro riferimenti verranno clonati e non i valori di ogni oggetto secondario.
Kamarey,

1
rendilo ricorsivo in modo che gli oggetti secondari vengano clonati in profondità.
fiatjaf,

solo curioso ... non sarà la variabile clone avrà i puntatori alle proprietà dell'oggetto originale? perché sembra che non ci siano nuove allocazioni di memoria
Rupesh Patel,

3
Sì. Questa è solo una copia superficiale, quindi il clone punterà agli stessi identici oggetti puntati dall'oggetto originale.
Mark Cidade,

Questa non è una risposta Stai letteralmente riempiendo un oggetto con riferimenti ad un altro oggetto. Apportare modifiche all'oggetto di origine apporterà modifiche al "clone".
Shawn Whinnery,

20

Shallow copy one-liner ( ECMAScript 5th edition ):

var origin = { foo : {} };
var copy = Object.keys(origin).reduce(function(c,k){c[k]=origin[k];return c;},{});

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

E copia superficiale one-liner ( ECMAScript 6th edition , 2015):

var origin = { foo : {} };
var copy = Object.assign({}, origin);

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

6
Questo può andare bene per oggetti semplici, ma copia solo i valori delle proprietà. Non tocca la catena del prototipo e usandolo Object.keyssalta le proprietà non enumerabili ed ereditate. Inoltre, perde descrittori di proprietà eseguendo l'assegnazione diretta.
Matt Bierner,

Se copi anche il prototipo, ti mancheranno solo i non enumerabili e i descrittori di proprietà, sì? Piuttosto buono. :)
sam,

Prestazioni a parte, questo è un modo davvero conveniente per copiare superficialmente un oggetto. Lo uso spesso per una sorta di proprietà di riposo false in un incarico destrutturante nei miei componenti di React.
mjohnsonengr,

17

Solo perché non ho visto menzionato AngularJS e ho pensato che le persone avrebbero potuto sapere ...

angular.copy fornisce anche un metodo per copiare in profondità oggetti e matrici.


oppure potrebbe essere usato allo stesso modo di jQiery extender:angular.extend({},obj);
Galvani,

2
@Galvani: Va notato che jQuery.extende angular.extendsono entrambe copie superficiali. angular.copyè una copia profonda.
Dan Atkinson,

16

Sembra che non ci sia ancora un operatore clone profondo ideale per oggetti simili a array. Come illustrato dal codice seguente, il clonatore jQuery di John Resig trasforma le matrici con proprietà non numeriche in oggetti che non sono matrici e il clonatore JSON di RegDwight rilascia le proprietà non numeriche. I seguenti test illustrano questi punti su più browser:

function jQueryClone(obj) {
   return jQuery.extend(true, {}, obj)
}

function JSONClone(obj) {
   return JSON.parse(JSON.stringify(obj))
}

var arrayLikeObj = [[1, "a", "b"], [2, "b", "a"]];
arrayLikeObj.names = ["m", "n", "o"];
var JSONCopy = JSONClone(arrayLikeObj);
var jQueryCopy = jQueryClone(arrayLikeObj);

alert("Is arrayLikeObj an array instance?" + (arrayLikeObj instanceof Array) +
      "\nIs the jQueryClone an array instance? " + (jQueryCopy instanceof Array) +
      "\nWhat are the arrayLikeObj names? " + arrayLikeObj.names +
      "\nAnd what are the JSONClone names? " + JSONCopy.names)

14
come altri hanno sottolineato nei commenti alla risposta di Resig, se si desidera clonare un oggetto simile a un array, si modifica {} in [] nella chiamata estesa, ad esempio jQuery.extend (true, [], obj)
Anentropic

15

Ho due buone risposte a seconda che il tuo obiettivo sia quello di clonare un "semplice vecchio oggetto JavaScript" o meno.

Supponiamo anche che la tua intenzione sia quella di creare un clone completo senza riferimenti prototipo all'oggetto sorgente. Se non sei interessato a un clone completo, puoi utilizzare molte delle routine Object.clone () fornite in alcune delle altre risposte (modello di Crockford).

Per i semplici vecchi oggetti JavaScript, un buon modo provato e vero per clonare un oggetto in runtime moderni è semplicemente:

var clone = JSON.parse(JSON.stringify(obj));

Si noti che l'oggetto di origine deve essere un oggetto JSON puro. Questo per dire che tutte le sue proprietà nidificate devono essere scalari (come booleano, stringa, matrice, oggetto, ecc.). Qualsiasi funzione o oggetto speciale come RegExp o Date non verrà clonato.

È efficiente? Cavolo si. Abbiamo provato tutti i tipi di metodi di clonazione e questo funziona meglio. Sono sicuro che alcuni ninja potrebbero evocare un metodo più veloce. Ma sospetto che stiamo parlando di guadagni marginali.

Questo approccio è semplice e facile da implementare. Avvolgilo in una funzione di convenienza e se hai davvero bisogno di spremere un po 'di guadagno, vai avanti in un secondo momento.

Ora, per gli oggetti JavaScript non semplici, non esiste una risposta davvero semplice. In effetti, non ci può essere a causa della natura dinamica delle funzioni JavaScript e dello stato dell'oggetto interno. La clonazione profonda di una struttura JSON con funzioni interne richiede di ricreare tali funzioni e il loro contesto interno. E JavaScript semplicemente non ha un modo standardizzato per farlo.

Il modo corretto per farlo, ancora una volta, è tramite un metodo pratico che dichiari e riutilizzi nel tuo codice. Il metodo pratico può essere dotato di una certa comprensione dei propri oggetti in modo da poter essere sicuri di ricreare correttamente il grafico all'interno del nuovo oggetto.

Abbiamo scritto il nostro, ma il miglior approccio generale che ho visto è trattato qui:

http://davidwalsh.name/javascript-clone

Questa è l'idea giusta L'autore (David Walsh) ha commentato la clonazione di funzioni generalizzate. Questo è qualcosa che potresti scegliere di fare, a seconda del tuo caso d'uso.

L'idea principale è che devi gestire in modo speciale l'istanza delle tue funzioni (o classi prototipiche, per così dire) in base al tipo. Qui, ha fornito alcuni esempi per RegExp e Date.

Questo codice non è solo breve, ma è anche molto leggibile. È abbastanza facile da estendere.

È efficiente? Cavolo si. Dato che l'obiettivo è quello di produrre un vero clone di deep-copy, dovrai camminare i membri del grafico dell'oggetto sorgente. Con questo approccio, puoi modificare esattamente quali membri figlio trattare e come gestire manualmente i tipi personalizzati.

Quindi eccoti. Due approcci. Entrambi sono efficienti dal mio punto di vista.


13

Questa non è generalmente la soluzione più efficiente, ma fa quello che mi serve. Casi di prova semplici di seguito ...

function clone(obj, clones) {
    // Makes a deep copy of 'obj'. Handles cyclic structures by
    // tracking cloned obj's in the 'clones' parameter. Functions 
    // are included, but not cloned. Functions members are cloned.
    var new_obj,
        already_cloned,
        t = typeof obj,
        i = 0,
        l,
        pair; 

    clones = clones || [];

    if (obj === null) {
        return obj;
    }

    if (t === "object" || t === "function") {

        // check to see if we've already cloned obj
        for (i = 0, l = clones.length; i < l; i++) {
            pair = clones[i];
            if (pair[0] === obj) {
                already_cloned = pair[1];
                break;
            }
        }

        if (already_cloned) {
            return already_cloned; 
        } else {
            if (t === "object") { // create new object
                new_obj = new obj.constructor();
            } else { // Just use functions as is
                new_obj = obj;
            }

            clones.push([obj, new_obj]); // keep track of objects we've cloned

            for (key in obj) { // clone object members
                if (obj.hasOwnProperty(key)) {
                    new_obj[key] = clone(obj[key], clones);
                }
            }
        }
    }
    return new_obj || obj;
}

Test della matrice ciclica ...

a = []
a.push("b", "c", a)
aa = clone(a)
aa === a //=> false
aa[2] === a //=> false
aa[2] === a[2] //=> false
aa[2] === aa //=> true

Test di funzionalita...

f = new Function
f.a = a
ff = clone(f)
ff === f //=> true
ff.a === a //=> false

11

AngularJS

Bene, se stai usando angolare, potresti farlo anche tu

var newObject = angular.copy(oldObject);

11

Non sono d'accordo con la risposta con i voti migliori qui . Un clone profondo ricorsivo è molto più veloce dell'approccio JSON.parse (JSON.stringify (obj)) menzionato.

Ed ecco la funzione per un rapido riferimento:

function cloneDeep (o) {
  let newO
  let i

  if (typeof o !== 'object') return o

  if (!o) return o

  if (Object.prototype.toString.apply(o) === '[object Array]') {
    newO = []
    for (i = 0; i < o.length; i += 1) {
      newO[i] = cloneDeep(o[i])
    }
    return newO
  }

  newO = {}
  for (i in o) {
    if (o.hasOwnProperty(i)) {
      newO[i] = cloneDeep(o[i])
    }
  }
  return newO
}

2
Mi è piaciuto questo approccio ma non gestisce correttamente le date; potresti aggiungere qualcosa di simile if(o instanceof Date) return new Date(o.valueOf());dopo aver verificato la null `
Luis

Arresti anomali su riferimenti circolari.
Harry

Nell'ultima versione stabile di Firefox, questo è molto più lungo rispetto alle altre strategie su quel collegamento Jsben.ch, di un ordine di grandezza o più. Batte gli altri nella direzione sbagliata.
WBT

11
// obj target object, vals source object
var setVals = function (obj, vals) {
    if (obj && vals) {
        for (var x in vals) {
            if (vals.hasOwnProperty(x)) {
                if (obj[x] && typeof vals[x] === 'object') {
                    obj[x] = setVals(obj[x], vals[x]);
                } else {
                    obj[x] = vals[x];
                }
            }
        }
    }
    return obj;
};

10

Solo quando è possibile utilizzare ECMAScript 6 o transpilers .

Caratteristiche:

  • Non si avvia getter / setter durante la copia.
  • Conserva getter / setter.
  • Conserva le informazioni sul prototipo.
  • Funziona con stili di scrittura OO sia letterali che funzionali .

Codice:

function clone(target, source){

    for(let key in source){

        // Use getOwnPropertyDescriptor instead of source[key] to prevent from trigering setter/getter.
        let descriptor = Object.getOwnPropertyDescriptor(source, key);
        if(descriptor.value instanceof String){
            target[key] = new String(descriptor.value);
        }
        else if(descriptor.value instanceof Array){
            target[key] = clone([], descriptor.value);
        }
        else if(descriptor.value instanceof Object){
            let prototype = Reflect.getPrototypeOf(descriptor.value);
            let cloneObject = clone({}, descriptor.value);
            Reflect.setPrototypeOf(cloneObject, prototype);
            target[key] = cloneObject;
        }
        else {
            Object.defineProperty(target, key, descriptor);
        }
    }
    let prototype = Reflect.getPrototypeOf(source);
    Reflect.setPrototypeOf(target, prototype);
    return target;
}

9

Ecco un metodo clone () completo che può clonare qualsiasi oggetto JavaScript. Gestisce quasi tutti i casi:

function clone(src, deep) {

    var toString = Object.prototype.toString;
    if (!src && typeof src != "object") {
        // Any non-object (Boolean, String, Number), null, undefined, NaN
        return src;
    }

    // Honor native/custom clone methods
    if (src.clone && toString.call(src.clone) == "[object Function]") {
        return src.clone(deep);
    }

    // DOM elements
    if (src.nodeType && toString.call(src.cloneNode) == "[object Function]") {
        return src.cloneNode(deep);
    }

    // Date
    if (toString.call(src) == "[object Date]") {
        return new Date(src.getTime());
    }

    // RegExp
    if (toString.call(src) == "[object RegExp]") {
        return new RegExp(src);
    }

    // Function
    if (toString.call(src) == "[object Function]") {

        //Wrap in another method to make sure == is not true;
        //Note: Huge performance issue due to closures, comment this :)
        return (function(){
            src.apply(this, arguments);
        });
    }

    var ret, index;
    //Array
    if (toString.call(src) == "[object Array]") {
        //[].slice(0) would soft clone
        ret = src.slice();
        if (deep) {
            index = ret.length;
            while (index--) {
                ret[index] = clone(ret[index], true);
            }
        }
    }
    //Object
    else {
        ret = src.constructor ? new src.constructor() : {};
        for (var prop in src) {
            ret[prop] = deep
                ? clone(src[prop], true)
                : src[prop];
        }
    }
    return ret;
};

Converte le primitive in oggetti wrapper, non una buona soluzione nella maggior parte dei casi.
Danubian Sailor

@DanubianSailor - Non credo che lo faccia ... sembra restituire le primitive fin dall'inizio, e non sembra far loro nulla che le trasformerebbe in oggetti wrapper quando vengono restituite.
Jimbo Jonny,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.