È un buon modo per clonare un oggetto in ES6?


155

Cercare su Google "oggetto clone javascript" porta a risultati davvero strani, alcuni sono irrimediabilmente obsoleti e altri sono troppo complessi, non è facile come solo:

let clone = {...original};

c'è qualcosa di sbagliato con questo?


1
questo non è legale ES6. Ma se così non fosse, questo non è un clone: ​​sia il tuo clone che le proprietà originali indicano ora le stesse cose. Ad esempio, original = { a: [1,2,3] }ti dà un clone con clone.aletteralmente essere original.a. La modifica attraverso sia cloneo originalmodifica la stessa cosa , quindi no, questo è male =)
Mike 'Pomax' Kamermans

2
@AlbertoRivera È un po ' valido JavaScript, in quanto è una proposta di fase 2 che probabilmente sarà un'aggiunta futura allo standard JavaScript.
Frxstrem,

@Frxstrem con la domanda su ES6, questo non è valido JavaScript =)
Mike 'Pomax' Kamermans

3
Clonazione superficiale o profonda?
Felix Kling,

2
Hai ragione, non è valido ES6, è valido ES9 . developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
mikemaccana

Risposte:


240

Questo è utile per la clonazione superficiale . La diffusione dell'oggetto è una parte standard di ECMAScript 2018 .

Per la clonazione profonda avrai bisogno di una soluzione diversa .

const clone = {...original} al clone superficiale

const newobj = {...original, prop: newOne} per aggiungere immutabilmente un altro oggetto all'originale e archiviarlo come nuovo oggetto.


18
Tuttavia, non è solo un clone superficiale? Come in, le proprietà non vengono clonate in modo ricorsivo, vero? Pertanto, original.innerObject === clone.innerObject e modificando original.innerObject.property cambierà clone.innerObject.property.
milanio,

18
sì, questo è un clone superficiale. se vuoi un clone profondo devi usareJSON.parse(JSON.stringify(input))
Mark Shust a M.academy il

8
/! \ JSON.parse (JSON.stringify (input)) incasina le date, non definito, ... Non è il proiettile d'argento per la clonazione! Vedi: maxpou.fr/immutability-js-without-library
Guillaume

1
Quindi l'hack JSON.stringify () / JSON.parse () è davvero il modo consigliato per clonare in profondità un oggetto in ES6? Continuo a vederlo raccomandato. Disturbing.
Solvitieg,

3
@MarkShust JSON.parse(JSON.stringify(input))non funzionerà, perché se presenti functionso infinitycome valori verranno semplicemente assegnati nullal loro posto. Funzionerà solo se i valori sono semplici literalse non lo sono functions.
barra rovesciataN

66

EDIT: quando questa risposta è stata pubblicata, la {...obj}sintassi non era disponibile nella maggior parte dei browser. Al giorno d'oggi, dovresti andare bene a usarlo (a meno che non sia necessario supportare IE 11).

Usa Object.assign.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

var obj = { a: 1 };
var copy = Object.assign({}, obj);
console.log(copy); // { a: 1 }

Tuttavia, questo non renderà un clone profondo. Non esiste ancora un modo nativo di clonazione profonda.

EDIT: Come KamMans 'Pomax' di @Mike menzionato nei commenti, puoi clonare in profondità oggetti semplici (es. Nessun prototipo, funzione o riferimento circolare) usando JSON.parse(JSON.stringify(input))


19
Ce n'è uno, a condizione che il tuo oggetto sia un vero oggetto letterale, e puramente dati, nel qual caso JSON.parse(JSON.stringify(input))è un vero clone profondo. Tuttavia, nel momento in cui prototipi, funzioni o riferimenti circolari sono in gioco, quella soluzione non funziona più.
Mike 'Pomax' Kamermans,

@ Mike'Pomax'Kamermans È vero. Perdere funzionalità per getter e setter è terribile, però ...
Alberto Rivera,

Se hai bisogno di una funzione generica per clonare in profondità qualsiasi oggetto, controlla stackoverflow.com/a/13333781/560114 .
Matt Browne,

1
Ora esiste un modo per eseguire la clonazione profonda in modo nativo .
Dan Dascalescu,

1
@DanDascalescu anche se è sperimentale, sembra piuttosto promettente. Grazie per le informazioni!
Alberto Rivera,

4

Se i metodi che hai utilizzato non funzionano bene con oggetti che coinvolgono tipi di dati come Date , prova questo

Importare _

import * as _ from 'lodash';

Oggetto clone profondo

myObjCopy = _.cloneDeep(myObj);

Basta import _ from 'lodash';è sufficiente. Ma +1 per la risposta "non reinventare la ruota".
Rustyx,

lodash è gonfio. Davvero non c'è bisogno di tirare dentro lodash solo per una semplice copia profonda. Molte altre soluzioni qui. Questa è una risposta davvero negativa per gli sviluppatori Web che desiderano creare un'app snella.
Jason Rice,

3

se non vuoi usare json.parse (json.stringify (oggetto)) puoi creare copie ricorsive di valori-chiave:

function copy(item){
  let result = null;
  if(!item) return result;
  if(Array.isArray(item)){
    result = [];
    item.forEach(element=>{
      result.push(copy(element));
    });
  }
  else if(item instanceof Object && !(item instanceof Function)){ 
    result = {};
    for(let key in item){
      if(key){
        result[key] = copy(item[key]);
      }
    }
  }
  return result || item;
}

Ma il modo migliore è creare una classe che può restituire un clone di se stesso

class MyClass{
    data = null;
    constructor(values){ this.data = values }
    toString(){ console.log("MyClass: "+this.data.toString(;) }
    remove(id){ this.data = data.filter(d=>d.id!==id) }
    clone(){ return new MyClass(this.data) }
}

2

In seguito alla risposta di @marcel ho scoperto che mancavano ancora alcune funzioni sull'oggetto clonato. per esempio

function MyObject() {
  var methodAValue = null,
      methodBValue = null

  Object.defineProperty(this, "methodA", {
    get: function() { return methodAValue; },
    set: function(value) {
      methodAValue = value || {};
    },
    enumerable: true
  });

  Object.defineProperty(this, "methodB", {
    get: function() { return methodAValue; },
    set: function(value) {
      methodAValue = value || {};
    }
  });
}

dove su MyObject ho potuto clonare il metodo A ma il metodo B è stato escluso. Ciò si è verificato perché mancante

enumerable: true

il che significa che non si è presentato

for(let key in item)

Invece sono passato a

Object.getOwnPropertyNames(item).forEach((key) => {
    ....
  });

che includerà chiavi non enumerabili.

Ho anche scoperto che il prototipo ( proto ) non è stato clonato. Per quello ho finito per usare

if (obj.__proto__) {
  copy.__proto__ = Object.assign(Object.create(Object.getPrototypeOf(obj)), obj);
}

PS: Frustrante che non sono riuscito a trovare una funzione integrata per farlo.


1

Puoi farlo anche in questo modo,

let copiedData = JSON.parse(JSON.stringify(data));

-1
We can do that with two way:
1- First create a new object and replicate the structure of the existing one by iterating 
 over its properties and copying them on the primitive level.

let user = {
     name: "John",
     age: 30
    };

    let clone = {}; // the new empty object

    // let's copy all user properties into it
    for (let key in user) {
      clone[key] = user[key];
    }

    // now clone is a fully independant clone
    clone.name = "Pete"; // changed the data in it

    alert( user.name ); // still John in the original object

2- Second we can use the method Object.assign for that 
    let user = { name: "John" };
    let permissions1 = { canView: true };
    let permissions2 = { canEdit: true };

    // copies all properties from permissions1 and permissions2 into user
    Object.assign(user, permissions1, permissions2);

  -Another example

    let user = {
      name: "John",
      age: 30
    };

    let clone = Object.assign({}, user);
It copies all properties of user into the empty object and returns it. Actually, the same as the loop, but shorter.

Ma Object.assign () non crea un clone profondo

let user = {
  name: "John",
  sizes: {
    height: 182,
    width: 50
  }
};

let clone = Object.assign({}, user);

alert( user.sizes === clone.sizes ); // true, same object

// user and clone share sizes
user.sizes.width++;       // change a property from one place
alert(clone.sizes.width); // 51, see the result from the other one

Per risolvere questo problema, dovremmo usare il ciclo di clonazione che esamina ogni valore dell'utente [chiave] e, se è un oggetto, replicare anche la sua struttura. Si chiama "clonazione profonda".

Esiste un algoritmo standard per la clonazione profonda che gestisce il caso sopra e casi più complessi, chiamato algoritmo di clonazione strutturata . Per non reinventare la ruota, possiamo usare un'implementazione funzionante dalla libreria JavaScript lodash, il metodo si chiama _.cloneDeep (obj) .


-1

Tutti i metodi sopra non gestiscono la clonazione profonda di oggetti in cui è nidificato su n livelli. Non ho verificato le sue prestazioni rispetto ad altri ma è breve e semplice.

Il primo esempio di seguito mostra la clonazione di oggetti usando Object.assignquali cloni fino al primo livello.

var person = {
    name:'saksham',
    age:22,
    skills: {
        lang:'javascript',
        experience:5
    }
}

newPerson = Object.assign({},person);
newPerson.skills.lang = 'angular';
console.log(newPerson.skills.lang); //logs Angular

Usando l'approccio sotto cloni profondi oggetto

var person = {
    name:'saksham',
    age:22,
    skills: {
        lang:'javascript',
        experience:5
    }
}

anotherNewPerson = JSON.parse(JSON.stringify(person));
anotherNewPerson.skills.lang = 'angular';
console.log(person.skills.lang); //logs javascript


JSON.parse / stringify è stato menzionato per anni come uno scarso metodo di clonazione profonda . Controlla le risposte precedenti e le domande correlate. Inoltre, questo non è nuovo a ES6.
Dan Dascalescu,

@DanDascalescu Lo so e penso che non dovrebbe essere un problema usarlo per oggetti semplici. Altri hanno anche menzionato questo nelle loro risposte nello stesso post e anche come commenti. Penso che non meriti un voto negativo.
Saksham,

Esatto - "altri hanno anche menzionato" JSON.parse / stringify nelle loro risposte. Perché pubblicare ancora un'altra risposta con la stessa soluzione?
Dan Dascalescu,
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.