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

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


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


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



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.