/ Truc et Astuce

Comment cloner (deepCopy) un object en Javascript ?

Comment cloner (deepCopy) un object en Javascript ?

Comme vous le savez certainement, en Javascript tout (ou presque) est référence !

Bien entendu les types primitifs[1], sont réellement copié, ce qui veut dire qu'un simple = permettra de "cloner" votre type primitif.

Voici un exemple:

// On crée notre première primitive
let primitive1 = 'Hello World';

// "Clone" de la primitive, en réalité, on récupère la valeur de la première primitive et on la stocke dans une nouvelle case mémoire (dans le cas des strings, c'est un ensemble de cases mémoires)
let primitiveCloned = primitive1;

console.log('Before');
console.log(`primitive1: ${primitive1}`);
console.log(`primitiveCloned: ${primitiveCloned}`);

// Before
// primitive1: HelloWorld
// primisiveCloned: HelloWorld

// Modifiaction de la deuxième primitive
primitiveCloned = 'Bonjour le monde';


console.log('After');
console.log(`primitive1: ${primitive1}`);
console.log(`primitiveCloned: ${primitiveCloned}`);

// Before
// primitive1: HelloWorld
// primisiveCloned: Bonjour le monde

Version CodePen

Maintenant qu'on a vu qu'un type Primitif (même une String est un type primitif en Javascript), nous allons se pencher sur les autres types ;)
En réalité en Javascript, il n'existe qu'un seul autre type ==> Object.
C'est ce que vous donnera l'opérateur typeof, car en Javascript tout est Object si ce n'est pas primitif !
Je vous conseille vivement de consulter cette page, car elle explique en détail les types en Javascript MDN.


Bien, maintenant attaquons nous au Object !

Si je "copie / clone" de la même manière qu'une primitive un objet et que je modifie le nouvel objet, l'original sera aussi modifié ?

Pour la démonstration, nous allons créer un objet:

// création de notre objet original
let mission = {
  name: 'Mission Impossible', 
  price: 194,
  address: '5 rue du supercodeur'
}

Maintenant que notre objet mission est créer, on tente de le copier ?

// "Copie" => en réalité ont stock la référence
let thisIsTheCopyMission = mission;

Ok, on essaye de modifier notre nouvel objet fraîchement "copié":

// On modifie en réalité la propriété `price` de l'objet mission, car on pointe directement dessus avec la référence
thisIsTheCopyMission.price = 21;

Enfin le moment de vérité !
On affiche le résultat:

console.log(`mission: ${mission}`);
// mission: {
//   name: 'Mission Impossible', 
//   price: 21,
//   address: '5 rue du supercodeur'
// }

console.log(`thisIsTheCopyMission: ${thisIsTheCopyMission}`);
// thisIsTheCopyMission: {
//   name: 'Mission Impossible', 
//   price: 21,
//   address: '5 rue du supercodeur'
// }

Attention, il n'existe pas en ES6 ni en ES7 (au moment où j'écris ces lignes) de méthodes officielle du langage permettant de faire de la deepCopy (les seules méthodes qui existent font de la shallowCopy (sois de la copie des primitives et des références pour les Object).
Il existe bien Object.assign(), mais attention elle copie que les propriétés énumérables et propres (pour faire simple, les propriétés qui ne sont pas héritées), voir MDN

Pour autant, le langage permet de réaliser ce type d'opération, c'est pour cela que vous avez deux solutions :

  • Utiliser une librairie (Lodash est la plus réputée)
  • Créer vous-même votre propre méthode

J'utilise régulièrement ce formidable petit outil jsben.ch, qui permet de tester, mesurer et comparer le temps d'exécution de différents scripts.
Extrêmement pratique pour voir comment rendre plus performant votre bout de code !
En plus, il y a des tests déjà enregistré, il suffit de browse et voir s'il n'y a pas déjà un test pour répondre à votre question.

C'est le cas pour moi, l'image ci-dessous est tiré d'un test déjà écrit :

Capture-du-2018-01-18-11-29-56
jsben.sh

Le graphique montre d'un coup d'œil la méthode la plus rapide (Recursive Copy Distinct).

Voici son code source:

// -------------- Recursive Copy Distinct -------------- //
const deepCopy = (block) => {
  let out = null;
  
  if (Array.isArray(block)) {
    out = [];
    for (let index = 0; index < block.length; ++index) {
      let subArray = block[index];
      out.push((typeof subArray === 'object') ? deepCopy(subArray) : subArray);
    }
  }
  else {
    out = {};
    for (let key in block) {
      let subObject = block[key];
      out[key] = (typeof subObject === 'object') ? deepCopy(subObject) : subObject;
    }
  }
  return out;
};

Bien, essayons cette petite pépite !

// Ici, je clone pour de bons :)
let myNewObj = deepCopy(mission);

// Uniquement myNewObj est modifié
myNewObj.price = 84;

console.log(`mission: ${mission}`);
// myNewObj: {
//   name: 'Mission Impossible', 
//   price: 21,
//   address: '5 rue du supercodeur'
// }

console.log(`myNewObj: ${myNewObj}`);
// thisIsTheCopyMission: {
//   name: 'Mission Impossible', 
//   price: 84,
//   address: '5 rue du supercodeur'
// }

Voilà, pour conclure cet article, j'espère que vous utiliserez plus régulièrement ce genre d'outils [2], c'est vital pour proposer des solutions optimisées :)

Est ce que vous utiliserez une lib type lodash ou copier, coller la fonction ci-dessus pour l'ajouter au projet ?

A mon travail l'application utilise la pire des méthodes, je vous laisse me dire dans les commentaires quelle méthode c'est :)

MAJ xx/05/2018 :
Il est plus agréable d'utiliser cette méthode custom une fois l'avoir inclut avec les autres méthodes vanilla de JavaScript et pas la copier / coller à chaque fois que vous en avez besoin (C'est pas pratique, ni DRY et non maintenable)

Ce qui est bien en JavaScript, c'est qu'on peux absolument tout modifier, c'est ce qui nous permet "d'injecter" notre custom method dans les prototypes des Objects :

// TODO: Implementer le bout de code permettant d'inclure notre custom methode dans les méthodes des objects

  1. Retrouvez la liste des types primitifs sur le site de MDN ↩︎

  2. http://jsben.ch ↩︎

DarkTerra

DarkTerra

Est un Developpeur spécialisé dans le Web et plus précisement coté Back-End. Ayant une attirance pour la performance et les nouvelles technologies, Node.JS correspond parfaitement à ses attentes.

Read More