Clonación profunda independiente de objetos en JavaScript

En cualquier lenguaje de programación, hay tipos de datos que los programadores describen a los sujetos para seguir trabajando y, si es necesario, procesarlos. JavaScript no es una excepción; tiene tipos de datos primitivos ( Number , String , Boolean , Symbol , etc.) y de referencia ( Array , Object , Function , Maps , Sets , etc.). Cabe señalar que los tipos de datos primitivos son inmutables: sus valores no pueden modificarse, sino que solo pueden sobrescribirse con un nuevo valor completo, pero con los tipos de datos de referencia sucede lo contrario. Por ejemplo, declare variables de tipo Number y Object :

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

No podemos modificar la variable num , solo podemos reescribir su valor, pero podemos modificar la variable obj:

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

Como puede ver, en el primer caso sobrescribimos el valor de la variable, y en el segundo expandimos el objeto. De esto concluimos que los tipos de datos primitivos no pueden expandirse, y con los tipos de datos de referencia podemos hacer esto, incluso con el modificador const .

Este último puede congelarse, por ejemplo, usando Object.freeze(obj) , pero este tema está más allá del alcance del artículo (enlaces para la curiosa Object.defineProperty , protegiendo el objeto de los cambios ).

¿Cómo se pasan los tipos de datos a las funciones en JavaScript? Cada programador js probablemente responderá fácilmente esta pregunta, pero, sin embargo, digamos: los tipos de datos primitivos siempre se pasan a una función solo por valor, y los referenciados siempre son solo por referencia. Y aquí con esto último, en algunas situaciones, surgen problemas. Veamos un ejemplo:

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

En este caso, simplemente declaramos un conjunto de números y lo mostramos en la consola. Ahora lo pasamos a una función que devuelve una nueva matriz, pero con la adición de algún valor en el segundo argumento, al final de la nueva matriz:

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

Como podemos ver en las conclusiones de la consola, no solo ha cambiado la nueva matriz, sino también la antigua. Esto sucedió porque en la función insertValToArr simplemente asignamos una matriz a otra const newArr = arr , y por lo tanto creamos un enlace a una matriz existente y cuando intentamos modificar una nueva matriz, se refería al área de memoria de la matriz anterior y, en términos generales, la cambió. Y dado que ambas matrices se refieren a la misma área de memoria, tendrán el mismo valor. Cambiemos nuestra función para que no pueda cambiar la matriz anterior:

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

La matriz anterior no ha cambiado, porque recibimos cada uno de sus elementos y asignamos individualmente los valores del elemento a los elementos de la nueva matriz. Ahora, este último tiene un área de memoria separada y, si la cambia, esto no afectará a la matriz anterior. Pero todos estos son ejemplos simples y en programas reales, lo más probable es que no solo se encuentren matrices unidimensionales, sino también bidimensionales, con menos frecuencia tridimensionales e incluso con menos frecuencia de cuatro dimensiones. En su mayoría se encuentran en forma de matrices asociativas (tablas hash). En JavaScript, estos son a menudo objetos.

Veamos los métodos estándar para copiar objetos que proporciona JavaScript: Object.assign () se usa para copiar los valores de todas sus propiedades enumeradas de uno o más objetos de origen al objeto de destino. Después de copiar, devuelve el objeto de destino. Considéralo:

 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 } 

Y de nuevo el viejo problema, nos referimos a la misma área de memoria, lo que lleva a la modificación de dos objetos a la vez: cambiar uno cambiará al otro. ¿Qué hacer si necesitamos obtener una copia de un objeto complejo (con múltiples ramificaciones) y, al mismo tiempo, cambiar el objeto, no modificar otro? Responder a esta pregunta es el propósito de este artículo. Además, consideraremos cómo escribir nuestro propio método para la clonación profunda (copia) de objetos de cualquier rama. Bajemos al código.

Paso 1: declare e inicialice el objeto Z , y también realice la salida de la consola para compararla antes y después de la clonación:

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

imagen

Paso 2: asigne el objeto Z al objeto refToZ para mostrar la diferencia entre la asignación normal y la clonación profunda:

 const refToZ = Z; 

Paso 3: asigne el objeto Z objeto Y utilizando la función deepClone y agregue una nueva propiedad al objeto Y Después de eso, muestre estos dos objetos en la consola:

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

imagen

imagen

En la consola, vemos claramente que al cambiar el objeto Y , al agregar una nueva propiedad, no cambiamos el objeto Z y este último no tendrá la propiedad addnlProp en su cuerpo.

Paso 4: cambie la propiedad x , que está en el cuerpo de los objetos Z e Y y vuelva a mostrar ambos objetos en la consola:

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

imagen

imagen

Al cambiar la misma propiedad en el objeto Y , no afectamos la propiedad en el cuerpo Z

Paso 5: en el último paso, para comparar, simplemente agregamos la propiedad addToZ con el valor 100 al objeto refToZ y mostramos los tres objetos en la consola:

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

imagen

imagen

imagen

Al cambiar el objeto refToZ también cambiamos Z , pero Y no se vio afectado. A partir de esto, concluimos que nuestra función crea un nuevo objeto independiente con propiedades y sus valores a partir de un objeto existente (el código para implementar la función deepClone se puede encontrar en CodePen ).

Detengámonos en la implementación de esta función. Este último encuentra cualquier anidamiento del objeto, sin siquiera saberlo. ¿Cómo hace ella esto? El caso es que en este caso usamos el conocido algoritmo para gráficos: búsqueda en profundidad. Un objeto es un gráfico que tiene una o varias ramas, que a su vez pueden tener sus ramas, etc. Para que podamos encontrar todo, necesitamos ir a cada rama y avanzar en su profundidad, por lo que encontraremos cada nodo en el gráfico y obtendremos sus valores. La búsqueda profunda se puede implementar de 2 maneras: recursividad y usando un bucle. El segundo puede resultar más rápido, ya que no llenará la pila de llamadas, lo que a su vez hace la recursividad. En nuestra implementación de la función deepClone utilizamos una combinación de recursión con un bucle. Si desea leer libros sobre algoritmos, le aconsejo que inicie los "algoritmos Grokayem" de Aditya Bhargava o un "Algoritmo: construcción y análisis" más profundo de Thomas Kormen.

Para resumir, recordamos los tipos de datos en JavaScript y cómo se pasan a las funciones. Observamos un ejemplo simple de clonación independiente de una matriz unidimensional simple. Consideramos una de las implementaciones de lenguaje estándar para copiar objetos y como resultado escribimos una función pequeña (en tamaño) para la clonación profunda independiente de objetos complejos. Una función similar puede encontrar su aplicación tanto en el lado del servidor (Nodo js), que es más probable, como en el cliente. Espero que este artículo te haya sido útil. Nos vemos de nuevo.

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


All Articles