Domanda Come faccio a clonare correttamente un oggetto JavaScript?


Ho un oggetto, x. Mi piacerebbe copiarlo come oggetto y, tale che cambia in y non modificare x. Mi sono reso conto che copiare oggetti derivati ​​da oggetti JavaScript incorporati comporta proprietà extra indesiderate. Questo non è un problema, dal momento che sto copiando uno dei miei oggetti letterali.

Come faccio a clonare correttamente un oggetto JavaScript?


2395


origine


risposte:


Risposta aggiornata

Basta usare Object.assign () come suggerito Qui

Ma tieni presente che questo rende solo una copia superficiale. Gli oggetti nidificati sono ancora copiati come riferimenti.


Risposta obsoleta

Fare questo per qualsiasi oggetto in JavaScript non sarà semplice o diretto. Ti imbatterai nel problema di raccogliere erroneamente gli attributi dal prototipo dell'oggetto che dovrebbero essere lasciati nel prototipo e non copiati nella nuova istanza. Se, per esempio, stai aggiungendo a clone metodo a Object.prototype, come mostrano alcune risposte, dovrai saltare esplicitamente quell'attributo. Ma cosa succede se ci sono altri metodi aggiuntivi aggiunti a Object.prototypeo altri prototipi intermedi di cui non sei a conoscenza? In tal caso, copi gli attributi che non dovresti, quindi devi rilevare gli attributi non previsti e non locali con hasOwnProperty metodo.

Oltre agli attributi non enumerabili, si verificherà un problema più difficile quando si tenta di copiare oggetti con proprietà nascoste. Per esempio, prototype è una proprietà nascosta di una funzione. Inoltre, il prototipo di un oggetto è referenziato con l'attributo __proto__, che è anche nascosto, e non verrà copiato da un ciclo for / in iterando sugli attributi dell'oggetto sorgente. credo __proto__ potrebbe essere specifico per l'interprete JavaScript di Firefox e potrebbe essere qualcosa di diverso negli altri browser, ma si ottiene l'immagine. Non tutto è enumerabile. Puoi copiare un attributo nascosto se conosci il suo nome, ma non conosco alcun modo per scoprirlo automaticamente.

Un altro ostacolo nella ricerca di una soluzione elegante è il problema di impostare correttamente l'ereditarietà del prototipo. Se il prototipo dell'oggetto sorgente è Object, quindi semplicemente creando un nuovo oggetto generale con {} funzionerà, ma se il prototipo della sorgente è un discendente di Object, quindi ti mancheranno i membri aggiuntivi da quel prototipo che hai saltato usando il hasOwnProperty filtro, o che erano nel prototipo, ma non erano enumerabili in primo luogo. Una soluzione potrebbe essere quella di chiamare l'oggetto di origine constructor proprietà per ottenere l'oggetto di copia iniziale e quindi copiare gli attributi, ma in tal caso non si otterranno attributi non enumerabili. Ad esempio, a Date oggetto memorizza i suoi dati come membro nascosto:

function clone(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    var copy = obj.constructor();
    for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
    }
    return copy;
}

var d1 = new Date();

/* Executes function after 5 seconds. */
setTimeout(function(){
    var d2 = clone(d1);
    alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString());
}, 5000);

La stringa della data per d1 sarà 5 secondi dietro a quello di d2. Un modo per farne uno Date lo stesso di un altro è chiamando il setTime metodo, ma questo è specifico per Date classe. Non penso che ci sia una soluzione generale a prova di proiettile per questo problema, anche se sarei felice di sbagliare!

Quando ho dovuto implementare una copia profonda generale, ho finito col compromettere assumendo che avrei solo dovuto copiare un piano Object, Array, Date, String, Number, o Boolean. Gli ultimi 3 tipi sono immutabili, quindi potrei eseguire una copia superficiale e non preoccuparmi che cambi. Ho inoltre assunto che qualsiasi elemento contenuto in Object o Array sarebbe anche uno dei 6 tipi semplici in quella lista. Questo può essere realizzato con un codice simile al seguente:

function clone(obj) {
    var copy;

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

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

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

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

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

La funzione sopra funzionerà in modo adeguato per i 6 tipi semplici che ho menzionato, purché i dati negli oggetti e nelle matrici formino una struttura ad albero. Cioè, non c'è più di un riferimento agli stessi dati nell'oggetto. Per esempio:

// This would be cloneable:
var tree = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "right" : null,
    "data"  : 8
};

// This would kind-of work, but you would get 2 copies of the 
// inner node instead of 2 references to the same copy
var directedAcylicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
directedAcyclicGraph["right"] = directedAcyclicGraph["left"];

// Cloning this would cause a stack overflow due to infinite recursion:
var cyclicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
cyclicGraph["right"] = cyclicGraph;

Non sarà in grado di gestire alcun oggetto JavaScript, ma potrebbe essere sufficiente per molti scopi, a patto che non si presuma che funzionerà solo per qualsiasi cosa venga lanciata.


1308



Con jQuery, puoi copia superficiale con estendere:

var copiedObject = jQuery.extend({}, originalObject)

le successive modifiche a copyObject non avranno effetto su originalObject e viceversa.

O per fare un copia profonda:

var copiedObject = jQuery.extend(true, {}, originalObject)

712



Se non si utilizzano le funzioni all'interno del proprio oggetto, una fodera molto semplice può essere la seguente:

var cloneOfA = JSON.parse(JSON.stringify(a));

Questo funziona per tutti i tipi di oggetti contenenti oggetti, matrici, stringhe, booleani e numeri.

Guarda anche questo articolo sull'algoritmo clone strutturato dei browser che viene utilizzato quando si pubblicano messaggi da e verso un lavoratore. Contiene anche una funzione per la clonazione profonda.


687



In ECMAScript 6 c'è Object.assign metodo, che copia i valori di tutte le proprietà enumerabili da un oggetto a un altro. Per esempio:

var x = {myProp: "value"};
var y = Object.assign({}, x); 

Ma tieni presente che gli oggetti nidificati vengono comunque copiati come riferimento.


508



Ci sono molte risposte, ma nessuna che menzioni Object.create da ECMAScript 5, che certamente non ti dà una copia esatta, ma imposta la sorgente come prototipo del nuovo oggetto.

Quindi, questa non è una risposta esatta alla domanda, ma è una soluzione a una linea e quindi elegante. E funziona meglio per 2 casi:

  1. Dove tale eredità è utile (duh!)
  2. Dove l'oggetto sorgente non verrà modificato, rendendo così la relazione tra i due oggetti un problema.

Esempio:

var foo = { a : 1 };
var bar = Object.create(foo);
foo.a; // 1
bar.a; // 1
foo.a = 2;
bar.a; // 2 - prototype changed
bar.a = 3;
foo.a; // Still 2, since setting bar.a makes it an "own" property

Perché considero questa soluzione superiore? È nativo, quindi senza loop, senza ricorsione. Tuttavia, i browser più vecchi avranno bisogno di un polyfill.


113



Un modo elegante per clonare un oggetto Javascript in una riga di codice

Un Object.assign il metodo 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à enumerabili di 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;
    }
  });
}

105



Per MDN:

  • Se vuoi una copia superficiale, usa Object.assign({}, a)
  • Per la copia "profonda", utilizzare JSON.parse(JSON.stringify(a))

Non c'è bisogno di librerie esterne ma è necessario controllare prima la compatibilità con il browser.


103



Se stai bene con una copia superficiale, la libreria underscore.js ha a clone metodo.

y = _.clone(x);

oppure puoi estenderlo come

copiedObject = _.extend({},originalObject);

70



Ci sono diversi problemi con la maggior parte delle soluzioni su Internet. Così ho deciso di fare un follow-up, che include, perché la risposta accettata non dovrebbe essere accettata.

situazione di partenza

voglio deep-copy un Javascript Object con tutti i suoi figli e i loro figli e così via. Ma dal momento che non sono una specie di sviluppatore normale, il mio Object ha normale  properties, circular structures e persino nested objects.

Quindi creiamo a circular structure e a nested object primo.

function Circ() {
    this.me = this;
}

function Nested(y) {
    this.y = y;
}

Portiamo tutto insieme in un Object di nome a.

var a = {
    x: 'a',
    circ: new Circ(),
    nested: new Nested('a')
};

Successivamente, vogliamo copiare a in una variabile denominata b e lo mutano.

var b = a;

b.x = 'b';
b.nested.y = 'b';

Sai cosa è successo qui perché se no non ti atterri nemmeno su questa grande domanda.

console.log(a, b);

a --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

Ora troviamo una soluzione.

JSON

Stavo usando il primo tentativo che ho provato JSON.

var b = JSON.parse( JSON.stringify( a ) );

b.x = 'b';
b.nested.y = 'b';

Non sprecare troppo tempo su di esso, otterrai TypeError: Converting circular structure to JSON.

Copia ricorsiva (la "risposta" accettata)

Diamo un'occhiata alla risposta accettata.

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

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

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

    // Handle Object
    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! Its type isn't supported.");
}

Sembra buono, eh? È una copia ricorsiva dell'oggetto e gestisce anche altri tipi, come Date, ma quello non era un requisito.

var b = cloneSO(a);

b.x = 'b';
b.nested.y = 'b';

Ricorsione e circular structures non funziona bene insieme ... RangeError: Maximum call stack size exceeded

soluzione nativa

Dopo aver discusso con il mio collega, il mio capo ci ha chiesto cosa è successo, e ha trovato un semplice soluzione dopo alcuni googlando. È chiamato Object.create.

var b = Object.create(a);

b.x = 'b';
b.nested.y = 'b';

Questa soluzione è stata aggiunta a Javascript qualche tempo fa e persino gestita circular structure.

console.log(a, b);

a --> Object {
    x: "a",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

... e vedi, non funzionava con la struttura annidata all'interno.

polyfill per la soluzione nativa

C'è un polyfill per Object.create nel browser più vecchio, proprio come l'IE 8. È simile a quello raccomandato da Mozilla e, naturalmente, non è perfetto e comporta lo stesso problema del soluzione nativa.

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

var b = clonePF(a);

b.x = 'b';
b.nested.y = 'b';

Ho messo F al di fuori del campo di applicazione in modo da poter dare un'occhiata a cosa instanceof ci dice

console.log(a, b);

a --> Object {
    x: "a",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> F {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

console.log(typeof a, typeof b);

a --> object
b --> object

console.log(a instanceof Object, b instanceof Object);

a --> true
b --> true

console.log(a instanceof F, b instanceof F);

a --> false
b --> true

Lo stesso problema del soluzione nativa, ma un po 'peggio uscita.

la migliore (ma non perfetta) soluzione

Quando ho scavato, ho trovato una domanda simile (In Javascript, quando si esegue una copia profonda, come posso evitare un ciclo, a causa di una proprietà che è "questo"?) a questo, ma con una soluzione migliore.

function cloneDR(o) {
    const gdcc = "__getDeepCircularCopy__";
    if (o !== Object(o)) {
        return o; // primitive value
    }

    var set = gdcc in o,
        cache = o[gdcc],
        result;
    if (set && typeof cache == "function") {
        return cache();
    }
    // else
    o[gdcc] = function() { return result; }; // overwrite
    if (o instanceof Array) {
        result = [];
        for (var i=0; i<o.length; i++) {
            result[i] = cloneDR(o[i]);
        }
    } else {
        result = {};
        for (var prop in o)
            if (prop != gdcc)
                result[prop] = cloneDR(o[prop]);
            else if (set)
                result[prop] = cloneDR(cache);
    }
    if (set) {
        o[gdcc] = cache; // reset
    } else {
        delete o[gdcc]; // unset again
    }
    return result;
}

var b = cloneDR(a);

b.x = 'b';
b.nested.y = 'b';

E diamo un'occhiata all'output ...

console.log(a, b);

a --> Object {
    x: "a",
    circ: Object {
        me: Object { ... }
    },
    nested: Object {
        y: "a"
    }
}

b --> Object {
    x: "b",
    circ: Object {
        me: Object { ... }
    },
    nested: Object {
        y: "b"
    }
}

console.log(typeof a, typeof b);

a --> object
b --> object

console.log(a instanceof Object, b instanceof Object);

a --> true
b --> true

console.log(a instanceof F, b instanceof F);

a --> false
b --> false

I requisiti sono abbinati, ma ci sono ancora alcuni problemi minori, tra cui la modifica del instance di nested e circ a Object.

La struttura degli alberi che condividono una foglia non verrà copiata, diventeranno due foglie indipendenti:

        [Object]                     [Object]
         /    \                       /    \
        /      \                     /      \
      |/_      _\|                 |/_      _\|  
  [Object]    [Object]   ===>  [Object]    [Object]
       \        /                 |           |
        \      /                  |           |
        _\|  |/_                 \|/         \|/
        [Object]               [Object]    [Object]

conclusione

L'ultima soluzione che utilizza la ricorsione e una cache, potrebbe non essere la migliore, ma è a vero copia profonda dell'oggetto. Gestisce semplice properties, circular structures e nested object, ma rovinerà l'istanza di loro durante la clonazione.

http://jsfiddle.net/einfallstoll/N4mr2/


63



Una soluzione particolarmente inelegante è l'uso della codifica JSON per creare copie profonde di oggetti che non hanno metodi membri. La metodologia è quella di codificare JSON dell'oggetto target, quindi decodificandolo si ottiene la copia che si sta cercando. Puoi decodificare tutte le volte che vuoi fare quante copie hai bisogno.

Naturalmente, le funzioni non appartengono a JSON, quindi funziona solo per oggetti senza metodi membro.

Questa metodologia era perfetta per il mio caso d'uso, dal momento che sto memorizzando i BLOB JSON in un archivio di valori-chiave, e quando sono esposti come oggetti in un'API JavaScript, ogni oggetto contiene effettivamente una copia dello stato originale dell'oggetto, quindi può calcolare il delta dopo che il chiamante ha mutato l'oggetto esposto.

var object1 = {key:"value"};
var object2 = object1;

object2 = JSON.stringify(object1);
object2 = JSON.parse(object2);

object2.key = "a change";
console.log(object1);// returns value

36



Per coloro che utilizzano AngularJS, esiste anche un metodo diretto per la clonazione o l'estensione degli oggetti in questa libreria.

var destination = angular.copy(source);

o

angular.copy(source, destination);

Altro in angular.copy documentazione...


23