Les objets sont les blocs fondamentaux de JavaScript. Un objet est une collection de propriétés, et une propriété est une association entre une clé (ou un nom) et une valeur. Presque tous les objets en JavaScript sont des instances de Object
qui se trouve au sommet de la chaîne des prototypes.
Introduction
Comme vous le savez, l’opérateur d’assignation ne crée pas une copie d’un objet, il lui attribue seulement une référence, regardons le code suivant :
let obj = { a: 1, b: 2,};let copy = obj;obj.a = 5;console.log(copy.a);// Result // a = 5;
La variable obj
est un conteneur pour le nouvel objet initialisé. La variable copy
pointe vers le même objet et est une référence à cet objet. Donc, en gros, cet objet { a: 1, b: 2, }
dit : Il y a maintenant deux façons d’avoir accès à moi. Vous devez passer par la variable obj
ou la variable copy
de l’une ou l’autre façon vous arrivez quand même à moi et tout ce que vous me faites par ces moyens (passerelles) m’affectera.
L’immuabilité est largement évoquée de nos jours et vous devez écouter cet appel ! Cette méthode supprime toute forme d’immutabilité et pourrait conduire à des bugs si l’objet original est utilisé par une autre partie de votre code.
La façon naïve de copier des objets
La façon naïve de copier des objets consiste à boucler l’objet original et à copier chaque propriété l’une après l’autre. Jetons un coup d’œil à ce code :
function copy(mainObj) { let objCopy = {}; // objCopy will store a copy of the mainObj let key; for (key in mainObj) { objCopy = mainObj; // copies each property to the objCopy object } return objCopy;}const mainObj = { a: 2, b: 5, c: { x: 7, y: 4, },}console.log(copy(mainObj));
Problèmes inhérents
-
objCopy
objet a une nouvelle méthodeObject.prototype
différente de la méthodemainObj
prototype d’objet, ce qui n’est pas ce que nous voulons. Nous voulons une copie exacte de l’objet original. - Les descripteurs de propriétés ne sont pas copiés. Un descripteur « writable » dont la valeur est fixée à false sera true dans l’objet
objCopy
. - Le code ci-dessus ne copie que les propriétés énumérables de
mainObj
. - Si l’une des propriétés de l’objet original est un objet lui-même, alors elle sera partagée entre la copie et l’original en faisant pointer leurs propriétés respectives vers le même objet.
Copie superficielle d’objets
On dit d’un objet qu’il est copié superficiellement lorsque les propriétés sources de haut niveau sont copiées sans aucune référence et qu’il existe une propriété source dont la valeur est un objet et qui est copiée comme référence. Si la valeur source est une référence à un objet, elle ne copie que cette valeur de référence dans l’objet cible.
Une copie peu profonde dupliquera les propriétés de niveau supérieur, mais l’objet imbriqué est partagé entre l’original(source) et la copie(cible).
Utilisation de la méthode Object.assign()
La méthode Object.assign() est utilisée pour copier les valeurs de toutes les propriétés propres énumérables d’un ou plusieurs objets sources vers un objet cible.
let obj = { a: 1, b: 2,};let objCopy = Object.assign({}, obj);console.log(objCopy);// Result - { a: 1, b: 2 }
Bien, cela fait le travail jusqu’à présent. Nous avons fait une copie de obj
. Voyons si l’immuabilité existe :
let obj = { a: 1, b: 2,};let objCopy = Object.assign({}, obj);console.log(objCopy); // result - { a: 1, b: 2 }objCopy.b = 89;console.log(objCopy); // result - { a: 1, b: 89 }console.log(obj); // result - { a: 1, b: 2 }
Dans le code ci-dessus, nous avons changé la valeur de la propriété 'b'
dans l’objet objCopy
en 89
et lorsque nous enregistrons l’objet objCopy
modifié dans la console, les changements ne s’appliquent qu’à objCopy
. La dernière ligne de code vérifie que l’objet obj
est toujours intact et n’a pas changé. Cela implique que nous avons créé avec succès une copie de l’objet source sans aucune référence à celui-ci.
Pitfall of Object.assign()
Pas si vite ! Alors que nous avons créé avec succès une copie et que tout semble fonctionner correctement, rappelez-vous que nous avons discuté de la copie superficielle ? Jetons un coup d’œil à cet exemple:
let obj = { a: 1, b: { c: 2, },}let newObj = Object.assign({}, obj);console.log(newObj); // { a: 1, b: { c: 2} }obj.a = 10;console.log(obj); // { a: 10, b: { c: 2} }console.log(newObj); // { a: 1, b: { c: 2} }newObj.a = 20;console.log(obj); // { a: 10, b: { c: 2} }console.log(newObj); // { a: 20, b: { c: 2} }newObj.b.c = 30;console.log(obj); // { a: 10, b: { c: 30} }console.log(newObj); // { a: 20, b: { c: 30} }// Note: newObj.b.c = 30; Read why..
Pourquoi obj.b.c = 30?
Eh bien, c’est un piège de Object.assign()
Object.assign
ne fait que des copies superficielles. Les deux newObj.b
et obj.b
partagent la même référence à l’objet car des copies individuelles n’ont pas été faites, au lieu de cela une référence à l’objet a été copiée. Toute modification apportée à l’une des propriétés de l’objet s’applique à toutes les références utilisant l’objet. Comment résoudre ce problème ? Continuez à lire… nous avons un correctif dans la section suivante.
Note : les propriétés sur la chaîne de prototypes et les propriétés non énumérables ne peuvent pas être copiées. Voir ici:
let someObj = { a: 2,}let obj = Object.create(someObj, { b: { value: 2, }, c: { value: 3, enumerable: true, },});let objCopy = Object.assign({}, obj);console.log(objCopy); // { c: 3 }
-
someObj
est sur la chaîne de prototype d’obj, donc il ne serait pas copié. -
property b
est une propriété non énumérable. -
property c
a un descripteur de propriété énumérable lui permettant d’être énumérable. C’est pourquoi il a été copié.
Copie profonde d’objets
Une copie profonde dupliquera chaque objet qu’elle rencontre. La copie et l’objet original ne partageront rien, il s’agira donc d’une copie de l’original. Voici la solution au problème que nous avons rencontré en utilisant Object.assign()
. Explorons.
Utilisation de JSON.parse(JSON.stringify(object));
Ceci corrige le problème que nous avions précédemment. Maintenant newObj.b
a une copie et non une référence ! C’est une façon de copier profondément des objets. Voici un exemple:
let obj = { a: 1, b: { c: 2, },}let newObj = JSON.parse(JSON.stringify(obj));obj.b.c = 20;console.log(obj); // { a: 1, b: { c: 20 } }console.log(newObj); // { a: 1, b: { c: 2 } } (New Object Intact!)
Immutable : ✓
Pitfall
Malheureusement, cette méthode ne peut pas être utilisée pour copier des méthodes d’objets définis par l’utilisateur. Voir ci-dessous.
Copier des méthodes d’objets
Une méthode est une propriété d’un objet qui est une fonction. Dans les exemples jusqu’à présent, nous n’avons pas copié un objet avec une méthode. Essayons-le maintenant et utilisons les méthodes que nous avons apprises pour faire des copies.
let obj = { name: 'scotch.io', exec: function exec() { return true; },}let method1 = Object.assign({}, obj);let method2 = JSON.parse(JSON.stringify(obj));console.log(method1); //Object.assign({}, obj)/* result{ exec: function exec() { return true; }, name: "scotch.io"}*/console.log(method2); // JSON.parse(JSON.stringify(obj))/* result{ name: "scotch.io"}*/
Le résultat montre que Object.assign()
peut être utilisé pour copier des méthodes alors que JSON.parse(JSON.stringify(obj))
ne peut pas être utilisé.
Copier des objets circulaires
Les objets circulaires sont des objets qui ont des propriétés qui se référencent elles-mêmes. Utilisons les méthodes de copie d’objets que nous avons apprises jusqu’à présent pour faire des copies d’un objet circulaire et voir si cela fonctionne.
Utilisation de JSON.parse(JSON.stringify(object))
Essayons JSON.parse(JSON.stringify(object))
:
// circular objectlet obj = { a: 'a', b: { c: 'c', d: 'd', },}obj.c = obj.b;obj.e = obj.a;obj.b.c = obj.c;obj.b.d = obj.b;obj.b.e = obj.b.c;let newObj = JSON.parse(JSON.stringify(obj));console.log(newObj);
Voici le résultat :
JSON.parse(JSON.stringify(obj))
ne fonctionne clairement pas pour les objets circulaires.
Utilisation de Object.assign()
Essayons Object.assign()
:
// circular objectlet obj = { a: 'a', b: { c: 'c', d: 'd', },}obj.c = obj.b;obj.e = obj.a;obj.b.c = obj.c;obj.b.d = obj.b;obj.b.e = obj.b.c;let newObj2 = Object.assign({}, obj);console.log(newObj2);
Voici le résultat :
Object.assign()
fonctionne bien pour la copie superficielle d’objets circulaires mais ne fonctionnerait pas pour la copie profonde. N’hésitez pas à explorer la circular object tree
sur la console de votre navigateur. Je suis sûr que vous y trouverez beaucoup de travail intéressant.
Utilisation des éléments d’étalement (… )
ES6 a déjà des éléments de repos pour l’affectation de déstructuration de tableau et des éléments d’étalement pour les littéraux de tableau implémentés. Jetez un coup d’œil à la mise en œuvre de l’élément spread sur un tableau ici:
const array = ;const newArray = ;console.log(newArray);// Result //
La propriété spread pour les littéraux d’objet est actuellement une proposition de phase 3 pour ECMAScript. Les propriétés de propagation dans les initialisateurs d’objets copient les propriétés énumérables propres d’un objet source sur l’objet cible. L’exemple ci-dessous montre comment il serait facile de copier un objet une fois que la proposition a été acceptée.
let obj = { one: 1, two: 2,}let newObj = { ...z };// { one: 1, two: 2 }
Note : Cela sera juste efficace pour la copie superficielle
Conclusion
Copier des objets en JavaScript peut être assez intimidant, surtout si vous êtes nouveau en JavaScript et ne connaissez pas votre chemin dans le langage. Espérons que cet article vous a aidé à comprendre et à éviter les futurs pièges que vous pourriez rencontrer en copiant des objets. Si vous avez une bibliothèque ou un morceau de code qui permet d’obtenir un meilleur résultat, n’hésitez pas à partager avec la communauté. Bon codage!