Clonage profond indépendant d'objets en JavaScript

Dans tout langage de programmation, il existe des types de données que les programmeurs décrivent aux sujets afin de poursuivre leur travail et, si nécessaire, de les traiter. JavaScript n'est pas une exception; il a des types de données primitifs ( Number , String , Boolean , Symbol , etc.) et de référence ( Array , Object , Function , Maps , Sets , etc.). Il convient de noter que les types de données primitifs sont immuables - leurs valeurs ne peuvent pas être modifiées, elles peuvent uniquement être remplacées par une nouvelle valeur complète, mais avec les types de données de référence, l'inverse est vrai. Par exemple, déclarez des variables de type Number et Object :

 let num = 5; let obj = { a: 5 }; 

Nous ne pouvons pas modifier la variable num , nous pouvons seulement réécrire sa valeur, mais nous pouvons modifier la variable obj:

 let num = 10; let obj = { a: 5, b: 6 }; 

Comme vous pouvez le voir, dans le premier cas, nous avons remplacé la valeur de la variable et dans le second, nous avons développé l'objet. De là, nous concluons que les types de données primitifs ne peuvent pas être développés, et avec les types de données de référence, nous pouvons le faire, même avec le modificateur const .

Ce dernier peut être figé, par exemple, en utilisant Object.freeze(obj) , mais cette rubrique dépasse le cadre de l'article (liens pour le curieux Object.defineProperty , protégeant l'objet des modifications ).

Comment les types de données sont-ils transmis aux fonctions en JavaScript? Chaque programmeur js répondra probablement facilement à cette question, mais néanmoins, disons: les types de données primitifs sont toujours passés à une fonction uniquement par valeur, et ceux référencés ne sont toujours que par référence. Et ici avec ce dernier, dans certaines situations, des problèmes se posent. Regardons un exemple:

 const arr = [0, 1, 2, 3, 4, 5]; console.log("Array: ", arr); // output: Array: [0, 1, 2, 3, 4, 5] 

Dans ce cas, nous avons simplement déclaré un tableau de nombres et l'avons affiché dans la console. Maintenant, nous le passons à une fonction qui retourne un nouveau tableau, mais avec l'ajout d'une valeur dans le deuxième argument, à la fin du nouveau tableau:

 const arr = [0, 1, 2, 3, 4, 5]; console.log("Old array: ", arr); // "Old array: " [0, 1, 2, 3, 4, 5] const newArr = insertValToArr(arr, 15); console.log("New array: ", newArr); // output: "New array: " [0, 1, 2, 3, 4, 5, 15] console.log("Old array: ", arr); // output: "Old array: " [0, 1, 2, 3, 4, 5, 15] function insertValToArr(arr, val) { const newArr = arr; newArr.push(val); return newArr; } 

Comme nous pouvons le voir dans les conclusions de la console, non seulement le nouveau tableau a changé, mais aussi l'ancien. Cela s'est produit parce que dans la fonction insertValToArr , nous venons d'affecter un tableau à un autre const newArr = arr , et const newArr = arr donc créé un lien vers un tableau existant et lorsque nous avons essayé de modifier un nouveau tableau, il faisait référence à la zone de mémoire de l'ancien tableau et, grosso modo, le changeait. Et puisque les deux tableaux font référence à la même zone de mémoire, ils auront la même valeur. Modifions notre fonction pour qu'elle ne puisse pas changer l'ancien tableau:

 const arr = [0, 1, 2, 3, 4, 5]; const newArr = insertValToArr(arr, 15); console.log("New array: ", newArr); // output: "New array: " [0, 1, 2, 3, 4, 5, 15] console.log("Old array: ", arr); // output: "Old array: " [0, 1, 2, 3, 4, 5] function insertValToArr(arr, val) { const newArr = []; arr.forEach((value, ind) => { newArr[ind] = value}); newArr.push(val); return newArr; } 

L'ancien tableau n'a pas changé, car nous avons reçu chacun de ses éléments et attribué individuellement les valeurs de l'élément aux éléments du nouveau tableau. Maintenant, ce dernier a une zone de mémoire distincte et si vous la changez, cela n'affectera pas l'ancien tableau. Mais tous ces exemples sont simples et dans les programmes réels, très probablement non seulement des tableaux unidimensionnels seront rencontrés, mais aussi bidimensionnels, moins souvent tridimensionnels et encore moins souvent quadridimensionnels. La plupart du temps, ils se présentent sous la forme de tableaux associatifs (tables de hachage). En JavaScript, ce sont le plus souvent des objets.

Examinons les méthodes standard de copie d'objets fournies par JavaScript - Object.assign () est utilisé pour copier les valeurs de toutes ses propres propriétés énumérées d'un ou plusieurs objets source vers l'objet cible. Après la copie, il renvoie l'objet cible. Considérez-le:

 const obj = { a: 1 }; const newObj = Object.assign({}, obj); console.log(newObj); // output: { a: 1, b: 2 } console.log(obj); // output: { a: 1, b: 2 } 

Et encore une fois l'ancien problème, nous nous référons à la même zone de mémoire, ce qui conduit à la modification de deux objets à la fois - changer l'un changera l'autre. Que faire si nous devons obtenir une copie d'un objet complexe (avec plusieurs branches) et en même temps, changer l'objet, ne pas en modifier un autre? Répondre à cette question est le but de cet article. De plus, nous verrons comment écrire notre propre méthode de clonage (copie) en profondeur d'objets de n'importe quelle branche. Passons au code.

Étape 1: déclarez et initialisez l'objet Z , et effectuez également une sortie console pour la comparaison avant et après le clonage:

 const Z = { a: 5, b: { g: 8, y: 9, t: { q: 48 } }, x: 47, l: { f: 85, p: { u: 89, m: 7 }, s: 71 }, r: { h: 9, a: 'test', s: 'test2' } }; console.log('Z object before cloning: ', Z); 

image

Étape 2: affectez l'objet Z à l'objet refToZ afin de montrer la différence entre l'affectation normale et le clonage profond:

 const refToZ = Z; 

Étape 3: affectez l'objet Z objet Y à l'aide de la fonction deepClone et ajoutez une nouvelle propriété à l'objet Y Après cela, affichez ces deux objets dans la console:

 const Y = deepClone(Z); function deepClone(obj) { const clObj = {}; for(const i in obj) { if (obj[i] instanceof Object) { clObj[i] = deepClone(obj[i]); continue; } clObj[i] = obj[i]; } return clObj; } Y.addnlProp = { fd: 45 }; console.log('Z object after cloning: ', Z); console.log('Y object: ', Y); 

image

image

Dans la console, nous voyons clairement que changer l'objet Y , ajouter une nouvelle propriété, nous ne changeons pas l'objet Z et ce dernier n'aura pas la propriété addnlProp dans son corps.

Étape 4: modifiez la propriété x , qui se trouve dans le corps des objets Z et Y et affichez à nouveau les deux objets dans la console:

 Yx = 76; console.log('Y object: ', Y); console.log('Z object: ', Z); 

image

image

En modifiant la même propriété dans l'objet Y , nous n'affectons pas la propriété dans le corps Z

Étape 5: à la dernière étape, à titre de comparaison, nous ajoutons simplement la propriété addToZ avec la valeur 100 à l'objet refToZ et refToZ les trois objets dans la console:

 refToZ.addToZ = 100; console.log('refToZ object: ', refToZ); console.log('Z object: ', Z); console.log('Y object: ', Y); 

image

image

image

En modifiant l'objet refToZ nous avons également modifié Z , mais Y pas Y affecté. De cela, nous concluons que notre fonction crée un nouvel objet indépendant avec des propriétés et leurs valeurs à partir d'un objet existant (le code pour implémenter la fonction deepClone peut être trouvé sur CodePen ).

Arrêtons-nous sur la mise en œuvre de cette fonction. Ce dernier trouve toute imbrication de l'objet, sans même le savoir. Comment fait-elle cela? Le fait est que dans ce cas, nous utilisons l'algorithme bien connu pour les graphiques - la recherche en profondeur. Un objet est un graphe qui a une ou plusieurs branches, qui à leur tour peuvent avoir leurs branches, etc. Pour que nous puissions tout trouver, nous devons aller dans chaque branche et aller dans sa profondeur, nous allons donc trouver chaque nœud dans le graphique et obtenir ses valeurs. La recherche approfondie peut être implémentée de 2 manières: la récursivité et l'utilisation d'une boucle. Le second peut s'avérer plus rapide, car il ne remplira pas la pile d'appels, qui à son tour effectue la récursivité. Dans notre implémentation de la fonction deepClone nous avons utilisé une combinaison de récursivité avec une boucle. Si vous voulez lire des livres sur les algorithmes, je vous conseille de démarrer Aditya Bhargava «Algorithmes Grokayem» ou un Thomas Kormen plus approfondi «Algorithmes: construction et analyse».

Pour résumer, nous avons rappelé les types de données en JavaScript et comment ils sont passés aux fonctions. Nous avons examiné un exemple simple de clonage indépendant d'un simple tableau unidimensionnel. Nous avons considéré l'une des implémentations de langage standard pour copier des objets et, par conséquent, écrit une petite fonction (en taille) pour le clonage profond indépendant d'objets complexes. Une fonction similaire peut trouver son application à la fois côté serveur (Node js), ce qui est plus probable, ainsi que sur le client. J'espère que cet article vous a été utile. A bientôt.

Source: https://habr.com/ru/post/fr480786/


All Articles