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

StackOverflow https://stackoverflow.com/questions/122102

  •  02-07-2019
  •  | 
  •  

Domanda

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

Ho fatto cose come obj = JSON.parse (JSON.stringify (o)); ma metti in dubbio l'efficienza.

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

È stato utile?

Soluzione

  

Nota 2019-giugno: questa era originariamente una risposta a un'altra risposta, non una risposta adeguata a questa domanda. Non ho idea del perché sia ??stata scelta come la risposta giusta. Ma dal momento che i voti sono cresciuti a dismisura ed è di gran lunga la risposta n. 1 a questa domanda, riassumerà le soluzioni come una risposta wiki.

Clonazione profonda nativa

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

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

Se non si utilizzano Date s, funzioni, undefined , Infinity , RegExps, Maps, Sets, BLOB, FileList, ImageDatas, matrici sparse, matrici tipizzate o altri tipi complessi all'interno del tuo oggetto, una linea molto semplice per clonare in profondità un oggetto è:

JSON.parse (JSON.stringify (oggetto))

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()

Vedi La risposta di Corban per i benchmark.

Clonazione affidabile utilizzando 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. Ad esempio,

ES6

Per completezza, si noti che ES6 offre due meccanismi di copia superficiale: Object.assign () e operatore di diffusione .

Altri suggerimenti

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 con deep impostato su true dal 10-20%).

jQuery.extend è piuttosto veloce quando il flag deep è impostato su false (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 cercando di clonare o puoi evitare array nidificati in profondità puoi scrivere un semplice ciclo per (var i in obj) per clonare il tuo oggetto mentre controlla 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 traccia JavaScript fanno schifo dell'ottimizzazione dei cicli for..in e il controllo di hasOwnProperty ti rallenta. Clone manuale quando la velocità è un must assoluto.

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

Fai attenzione usando il metodo JSON.parse (JSON.stringify (obj)) su oggetti Date - JSON.stringify (new Date ()) restituisce una rappresentazione in formato stringa della data in formato ISO, che JSON.parse () non non converte nuovamente in un oggetto Date . Vedi questa risposta per maggiori dettagli .

Inoltre, tieni presente che, almeno in Chrome 65, la clonazione nativa non è la strada da percorrere. Secondo questo JSPerf , eseguire la clonazione nativa creando una nuova funzione è quasi 800x più lento rispetto all'uso 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);

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

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

Clonazione strutturata

Lo standard HTML include un algoritmo strutturato interno di clonazione / serializzazione 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 causerebbero errori per JSON.

Supporto in Node.js: sperimentale ??

Il modulo v8 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? ??

Attualmente i browser non forniscono un'interfaccia diretta per l'algoritmo di clonazione strutturata, ma una funzione globale strutturataClone () è 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 è pubblicare i dati attraverso una porta di un MessageChannels . L'altra porta emetterà un evento message con un clone strutturato del .data allegato. Sfortunatamente, ascoltare 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. Qui ci sono un paio di hack poco pratici.

history.pushState () e history.replaceState () creano entrambi 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:

'use strict';

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

  const clone = structuredClone(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.");
};

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

main();

Sebbene sia 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 costruttore Notification 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:

'use strict';

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

  const clone = structuredClone(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.");
};

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

main();

Se non ce n'era uno incorporato, puoi 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;
}

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

Un Object.assign 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 polifill 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;
    }
  });
}

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

Prova:

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);

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

Copia approfondita per performance: Classificato dal migliore al peggiore

  • Riassegnazione " = " (array di stringhe, solo array di numeri)
  • Slice (array di stringhe, solo array di numeri)
  • Concatenazione (array di stringhe, solo array di numeri)
  • Funzione personalizzata: copia for-loop o ricorsiva
  • $ .extend $ di jQuery
  • JSON.parse (array di stringhe, array di numeri, array di oggetti - solo)
  • Underscore.js 's _.clone (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);

Utilizza 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:

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

Esiste una libreria (chiamata "clone") , che 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 sia per il browser che per Node.js.

Ecco un esempio su come usarlo:

Installalo con

npm install clone

o impacchettalo con Ender .

ender build clone [...]

Puoi anche scaricare il codice sorgente manualmente.

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.)

So che questo è un vecchio post, ma ho pensato che 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);

La clonazione di un oggetto è sempre stata una preoccupazione in JS, ma era tutto prima di ES6, elenchiamo diversi modi di copiare un oggetto in JavaScript sotto, immagino di avere l'oggetto sotto e vorrei avere una copia profonda di ciò:

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

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

1) ES5 +, Usare 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 +, usando 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 & amp; Loadash:

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

Spero che questi aiuti ...

Se lo stai utilizzando, la Underscore.js ha una clone metodo.

var newObject = _.clone(oldObject);

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 link _.cloneDeep di Lo-Dash lodash

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.Uso del link Underscore.js _.clone Underscore.js

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.)

MA SBAGLIATO QUANDO

<*>

Reference medium.com

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

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 simpleoo .

Modifica

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

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));

Lodash ha un bel _.cloneDeep (valore) metodo:

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

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }

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 libreria.


Modifica

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

var newObject = Object.create(oldObject);

NOTA: se usi parte di questo, potresti avere problemi con alcune iterazioni che usano hasOwnProperty . Perché create crea un nuovo oggetto vuoto che eredita oldObject . Ma è ancora utile e pratico per clonare oggetti.

Ad esempio se oldObject.a = 5;

newObject.a; // is 5

ma

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

One-liner copia superficiale ( 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

One-liner copia superficiale ( 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

Solo perché non ho visto AngularJS menzionato e ho pensato che le persone potrebbero voler sapere. ..

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

Sembra che non ci sia ancora un operatore di 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)

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

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 è semplicemente semplice e facile da implementare. Avvolgilo in una funzione di convenienza e se hai davvero bisogno di spremere un po 'di guadagno, vai 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.

Non solo questo codice è breve, ma è anche molto leggibile. È abbastanza facile da estendere.

È efficiente? Cavolo si. Dato che l'obiettivo è quello di produrre un vero clone di copia profonda, 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.

Questa non è generalmente la soluzione più efficiente, ma fa quello di cui ho bisogno. 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 di array ciclici ...

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 funzionale ...

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

AngularJS

Bene, se stai usando l'angolazione, potresti farlo anche

var newObject = angular.copy(oldObject);

Non sono d'accordo con la risposta con il massimo dei voti qui . Un Deep Clone ricorsivo è molto più veloce rispetto all'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
}
// 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;
};

Per le persone che desiderano utilizzare la versione JSON.parse (JSON.stringify (obj)) , ma senza perdere gli oggetti Date, è possibile utilizzare la secondo argomento del metodo parse a riconvertire le stringhe in Date:

function clone(obj) {
  var regExp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
  return JSON.parse(JSON.stringify(x), function(k, v) {
    if (typeof v === 'string' && regExp.test(v))
      return new Date(v);
    return v;
  });
}

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;
};
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top