Cloner un objet signifie créer un nouvel objet possédant les mêmes propriétés que l'original. En JavaScript, les objets sont stockés par référence, ce qui implique que lorsqu'on modifie une propriété d'un objet, cela peut impacter d'autres variables (qui auraient la même référence).


const userOne = { name: 'John', age: 31 };
const userTwo = userOne;

userTwo.name = 'Sam';
console.log(userOne.name); // Sam, car userOne a la même référence que userTwo 


L'objectif principal lorsqu'on clone un objet est de copier des données que l'on pourra ensuite modifier sans affecter l'objet original. C'est particulièrement utile lorsqu'on travaille avec des outils reposant sur l'immutabilité comme NgRx ou Redux.


Selon l'objet que l'on veut cloner, il est important de choisir la bonne méthode. En voici 3 différentes, ainsi qu'une 4ème en bonus.


Spread operator ou Object.assign()


Cette première méthode a l'avantage d'être très simple et rapide, mais elle ne permet que d'effecter des copies superficielles ("Shallow clone"). C'est à dire qu'elle n'est pas récursive, et ne clonera pas les sous-objets.


const userOne = { name: 'John', age: 31, settings: { autoSave: true, keyboardNavigation: false } };
const userTwo = { ...userOne }; // ou Object.assign({}, userOne)

userTwo.name = 'Sam';
console.log(userOne.name); // John 👍 

userTwo.settings.autoSave = false;
console.log(userOne.settings.autoSave); // false: la valeur de l'objet original a changé 👎


Nous pouvons donc utiliser cette méthode lorsqu'on est sûr que l'objet original ne contient pas de sous-objets. Pour effectuer un clonage récursif, nous pouvons passer à la section suivante avec JSON.stringify();


JSON.stringify()


Cette méthode consiste à créer une nouvelle variable avec JSON.stringify() et de la parser pour obtenir un nouvel objet.


const userOne = { name: 'John', age: 31, settings: { autoSave: true, keyboardNavigation: false }, registrationDate: new Date('2021-12-28') };
const userTwo = JSON.parse(JSON.stringify(userOne));

userTwo.name = 'Sam';
console.log(userOne.name); // John 👍

userTwo.settings.autoSave = false;
console.log(userOne.settings.autoSave); // true 👍


Néanmoins, cette méthode a quelques limites : si l'objet original stocke des propriétés comme des types Date, fonctions, undefined, Infinity, RegExps, Maps, Sets, Blobs, Typed Arrays ou d'autres types complexes, ces données seront converties en chaine de caractères et donc perdues.


console.log(typeof userOne.registration); // objet date 👍
console.log(typeof userTwo.registration); // string 👎


Nous pouvons donc utiliser cette méthode si notre objet possède des sous-objets, mais il ne devra par contre n'avoir que des valeurs primitives et des tableaux. Si nous avons besoin de gérer des objets plus complexes, nous avons besoin de l'aide d'une librairie externe qui fournit un outil pour cela : par exemple, cloneDeep() de lodash.


Lodash cloneDeep


La méthode cloneDeep() de lodash est un "deep clone" bien plus robuste que JSON.parse(JSON.stringify()). Elle gère beaucoup de cas aux limites (comme par exemple les dates), et contrairement au spread operator, elle est récursive.


Elle s'utilise comme cela :

const userOne = { name: 'John', age: 31, settings: { autoSave: true, keyboardNavigation: false } };
const userTwo = _.cloneDeep(userOne); 


Cette méthode peut aussi être importée séparément via le lodash.clonedeep module et c'est cela que je recommanderais, si c'est la seule fonction de lodash que vous comptez utiliser.


cloneDeep peut être le meilleur choix si vous avez des objets complexes à gérer, mais pourra impacter fortement la performance de l'application. Je détaillerai dans un futur article les différences en termes de performances des 3 méthodes détaillées.


Si vous savez au contraire que vous pouvez utiliser une des deux méthodes précédentes, alors n'hésitez pas.


Bonus : cloner nativement avec structuredClone


La méthode globale structuredClone sera bientôt disponible dans tous les principaux navigateurs. On pourra l'utiliser comme cela :


const clone = structuredClone(original);