Est-ce un bon moyen de cloner un objet dans ES6?

155

La recherche sur Google de "javascript clone object" apporte des résultats vraiment étranges, certains d'entre eux sont désespérément obsolètes et certains sont tout simplement trop complexes, n'est-ce pas aussi simple que:

let clone = {...original};

Y a-t-il quelque chose de mal à cela?

Dmitry Fadeev
la source
1
ce n'est pas légal ES6. Mais si ce n'était pas le cas, ce n'est pas un clone: ​​votre clone et vos propriétés d'origine pointent maintenant vers les mêmes choses. Par exemple, original = { a: [1,2,3] }vous donne un clone avec clone.aêtre littéralement original.a. Modification via l'un cloneou l' autre ou originalmodifie la même chose , donc non, c'est mauvais =)
Mike 'Pomax' Kamermans
2
@AlbertoRivera C'est un peu du JavaScript valide, en ce sens qu'il s'agit d'une proposition de stade 2 qui sera probablement un ajout futur à la norme JavaScript.
Frxstrem
@Frxstrem avec la question concernant ES6, ce n'est pas valide JavaScript =)
Mike 'Pomax' Kamermans
3
Clonage superficiel ou profond?
Felix Kling
2
Vous avez raison, ce n'est pas ES6 valide, c'est ES9 valide . developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
mikemaccana

Réponses:

240

C'est bon pour le clonage superficiel . La diffusion d'objets fait partie intégrante d'ECMAScript 2018 .

Pour le clonage profond, vous aurez besoin d'une solution différente .

const clone = {...original} cloner peu profond

const newobj = {...original, prop: newOne} pour ajouter immuablement un autre accessoire à l'original et le stocker en tant que nouvel objet.

Mark Shust à M.academy
la source
18
Cependant, n'est-ce pas juste un clone peu profond? Comme dans, les propriétés ne sont pas clonées de manière récursive, n'est-ce pas? Par conséquent, original.innerObject === clone.innerObject et la modification de original.innerObject.property changeront clone.innerObject.property.
milanio
18
oui, c'est un clone superficiel. si vous voulez un clone profond, vous devez utiliserJSON.parse(JSON.stringify(input))
Mark Shust à M.academy
8
/! \ JSON.parse (JSON.stringify (input)) gâche les dates, indéfinies, ... Ce n'est pas la solution miracle pour le clonage! Voir: maxpou.fr/immutability-js-without-library
Guillaume
1
Le hack JSON.stringify () / JSON.parse () est-il vraiment le moyen recommandé pour cloner en profondeur un objet dans ES6? Je continue à le voir recommandé. Inquiétant.
Solvitieg
3
@MarkShust JSON.parse(JSON.stringify(input))ne fonctionnera pas, car s'il existe functionsou en infinitytant que valeurs, il les attribuera simplement nullà leur place. Cela ne fonctionnera que si les valeurs sont simples literalset non functions.
backslashN
66

EDIT: Lorsque cette réponse a été publiée, {...obj} syntaxe n'était pas disponible dans la plupart des navigateurs. De nos jours, vous devriez bien l'utiliser (à moins que vous n'ayez besoin de prendre en charge IE 11).

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

Cependant, cela ne fera pas un clone profond. Il n'existe pas encore de méthode native de clonage profond.

EDIT: Comme @Mike 'Pomax' Kamermans l'a mentionné dans les commentaires, vous pouvez cloner en profondeur des objets simples (c'est-à-dire pas de prototypes, de fonctions ou de références circulaires) en utilisant JSON.parse(JSON.stringify(input))

Alberto Rivera
la source
19
Il y en a un, à condition que votre objet soit un véritable objet littéral, et purement des données, auquel cas il JSON.parse(JSON.stringify(input))s'agit d'un clone profond approprié. Cependant, au moment où des prototypes, des fonctions ou des références circulaires sont en jeu, cette solution ne fonctionne plus.
Mike 'Pomax' Kamermans le
@ Mike'Pomax'Kamermans C'est vrai. Perdre des fonctionnalités pour les getters et les setters est terrible, cependant ...
Alberto Rivera
Si vous avez besoin d'une fonction générique pour cloner en profondeur un objet, consultez stackoverflow.com/a/13333781/560114 .
Matt Browne
1
Il existe maintenant un moyen de faire du clonage profond de manière native .
Dan Dascalescu
1
@DanDascalescu même si c'est expérimental, cela semble plutôt prometteur. Merci pour l'info!
Alberto Rivera
4

Si les méthodes que vous avez utilisées ne fonctionnent pas correctement avec des objets impliquant des types de données tels que Date , essayez ceci

Importer _

import * as _ from 'lodash';

Objet clone profond

myObjCopy = _.cloneDeep(myObj);
shaheer shukur
la source
C'est juste import _ from 'lodash';suffisant. Mais +1 pour la réponse "ne réinventez pas la roue".
rustyx
lodash est gonflé. Vraiment pas besoin de tirer dans lodash juste pour une simple copie profonde. Beaucoup d'autres solutions ici. C'est une très mauvaise réponse pour les développeurs Web qui cherchent à créer une application allégée.
Jason Rice
3

si vous ne souhaitez pas utiliser json.parse (json.stringify (object)), vous pouvez créer des copies clé-valeur de manière récursive:

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

Mais le meilleur moyen est de créer une classe qui peut renvoyer un clone d'elle-même

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) }
}
Marcel
la source
2

Suite à la réponse de @marcel, j'ai trouvé que certaines fonctions manquaient toujours sur l'objet cloné. par exemple

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

où sur MyObject je pourrais cloner methodA mais methodB a été exclu. Cela s'est produit parce qu'il manque

enumerable: true

ce qui signifiait qu'il n'apparaissait pas dans

for(let key in item)

Au lieu de cela, je suis passé à

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

qui comprendra des clés non énumérables.

J'ai également constaté que le prototype ( proto ) n'était pas cloné. Pour cela, j'ai fini par utiliser

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

PS: Frustrant que je n'ai pas pu trouver une fonction intégrée pour faire cela.

Shane Gannon
la source
1

Vous pouvez aussi le faire comme ça,

let copiedData = JSON.parse(JSON.stringify(data));
rafee_que_
la source
-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.

Mais Object.assign () ne crée pas de clone profond

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

Pour résoudre ce problème, nous devrions utiliser la boucle de clonage qui examine chaque valeur de l'utilisateur [clé] et, s'il s'agit d'un objet, répliquer également sa structure. Cela s'appelle un «clonage en profondeur».

Il existe un algorithme standard pour le clonage profond qui gère le cas ci-dessus et les cas plus complexes, appelé algorithme de clonage structuré . Afin de ne pas réinventer la roue, nous pouvons utiliser une implémentation fonctionnelle de celle-ci à partir de la bibliothèque JavaScript lodash, la méthode s'appelle _.cloneDeep (obj) .

Mohamed Elshahawy
la source
-1

Toutes les méthodes ci-dessus ne gèrent pas le clonage en profondeur des objets où il est imbriqué à n niveaux. Je n'ai pas vérifié ses performances par rapport aux autres mais c'est court et simple.

Le premier exemple ci-dessous montre le clonage d'objets en utilisant Object.assignquels clones jusqu'au premier niveau.

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

Utilisation de l'objet de clones profonds d'approche ci-dessous

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

Saksham
la source
JSON.parse / stringify a été mentionné comme une mauvaise méthode de clonage profond depuis des années . Veuillez vérifier les réponses précédentes, ainsi que les questions connexes. De plus, ce n'est pas nouveau pour ES6.
Dan Dascalescu
@DanDascalescu Je le sais et je pense qu'il ne devrait pas être un problème de l'utiliser pour des objets simples. D'autres l'ont également mentionné dans leurs réponses dans le même post et même dans les commentaires. Je pense que cela ne mérite pas un vote défavorable.
Saksham
Exactement - "d'autres ont également mentionné" JSON.parse / stringify dans leurs réponses. Pourquoi publier une autre réponse avec la même solution?
Dan Dascalescu