Copiando objetos con JS

Al programar con JavaScript muchas veces nos enfrentamos al problema de copiar y modificar un objeto sin mutar el objeto original. ¿Por qué? ¿Cómo podemos resolverlo? ¿Acaso hay una forma novedosa de hacerlo? (Si la hay, "structuredClone").

Miguel Segura

Miguel Segura

Copiando objetos con JS

Una variable puede contener uno de los siguientes valores primitivos:

  • Boolean
  • Null
  • Undefined
  • String
  • Number

Y nosotros podemos duplicarla con otro nombre y modificarla libremente sin ningún problema:

let persona1 = 'Soy Mike'
let persona2 = a

persona2 = 'Soy Becky'

console.log(persona1) // 'Soy Mike'
console.log(persona2) // 'Soy Becky'

Hasta aquí una variable funciona como una cajita en la cual podemos guardar valores de manera independiente. A cada variable le corresponde un valor.

Todo es intuitivoTodo es intuitivo

Sin embargo, todo cambia cuando declaramos un objeto, ya que creamos una referencia a un espacio de la memoria donde se ha guardado el objeto.

let persona1 = { nombre: 'Mike' }
let persona2 = persona1

persona2.nombre = 'Becky'

console.log(persona1.nombre) // 'Becky'
console.log(persona2.nombre) // 'Becky'

¿Por qué se modificó el nombre de persona1 al cambiar el de persona 2? Porque a persona2 le asignamos la referencia a persona1.

Una variable puede hacer referencia a un espacio en memoriaUna variable puede hacer referencia a un espacio en memoria

¿Cómo solucionamos esto? ¿Como duplicamos un objeto y cambiamos ciertas propiedades sin afectar el original?

Existen dos soluciones "superficiales" y dos "profundas", vamos a analizarlas.

El operador Spread

let persona1 = { nombre: 'Mike', hobby: "bailar" }
let persona2 = { ...persona1 }

persona2.nombre = 'Becky'

Object.assign

let persona1 = { nombre: 'Mike', hobby: "bailar" }
let persona2 = Object.assign({}, persona1)

persona2.nombre = 'Becky'

En ambos casos tendremos dos "personas" que comparten un hobby, pero con nombres diferentes y podremos utilizar ambos objetos independientemente.

console.log(`Soy ${persona1.nombre} y amo ${persona1.hobby}`)
// Soy Mike y amo bailar
console.log(`Soy ${persona2.nombre} y amo ${persona2.hobby}`)
// Soy Becky y amo bailar

La limitación del operador spread y de Object.assign la encontramos cuando nuestro objeto tiene otros objetos anidados, por ejemplo:

let persona1 = {
  nombre: 'Mike',
  hobby: "bailar",
  pais: {
    nombre: "México",
  }
}
let persona2 = { ...persona1 }

persona2.nombre = 'Becky'
persona2.pais.nombre = 'Inglaterra'

console.log(`Soy ${persona1.nombre} y naci en ${persona1.pais.nombre}`)
// Soy Mike y naci en Inglaterra
console.log(`Soy ${persona2.nombre} y naci en ${persona2.pais.nombre}`)
// Soy Becky y naci en Inglaterra

Como puedes ver pudimos modificar el nombre exitosamente, sin embargo, al tratar de modificar el país que se encuentra en un objeto anidado volvimos al problema anterior donde los objetos no son totalmente independientes. En estos casos tenemos que utilizar copias "profundas" del objeto:

JSON

Es la manera comúnmente aceptada de clonar un objeto profundamente sin recurrir a librerías como lodash

let persona1 = {
  nombre: 'Mike',
  hobby: "bailar",
  pais: {
    nombre: "México",
  }
}
let persona2 = JSON.parse(JSON.stringify(persona1))

persona2.nombre = 'Becky'
persona2.pais.nombre = 'Inglaterra'

console.log(`Soy ${persona1.nombre} y naci en ${persona1.pais.nombre}`)
// Soy Mike y naci en Mexico
console.log(`Soy ${persona2.nombre} y naci en ${persona2.pais.nombre}`)
// Soy Becky y naci en Inglaterra

La novedosa: structuredClone

El método globalstructuredClone() genera una copia profunda de un objeto empleando un algoritmo de clonación estructurada que copia recursivamente un objeto apoyándose de un mapa de referencias para evitar bucles infinitos.

Su funcionamiento interno suena rebuscado, pero es muy elegante en la práctica.

let persona1 = {
  nombre: 'Mike',
  hobby: "bailar",
  pais: {
    nombre: "México",
  }
}
let persona2 = structuredClone(persona1)

persona2.nombre = 'Becky'
persona2.pais.nombre = 'Inglaterra'

console.log(`Soy ${persona1.nombre} y naci en ${persona1.pais.nombre}`)
// Soy Mike y naci en Mexico
console.log(`Soy ${persona2.nombre} y naci en ${persona2.pais.nombre}`)
// Soy Becky y naci en Inglaterra

Ya está disponible en los navegadores más modernos, (excepto Safari) sin embargo, ya hay polyfills para utilizarlo en navegadores antiguos

Puedes consultar toda la documentación al respecto aquí.

Cuéntame que te pareció el post y sobre que te gustaría que hablara en mis redes sociales o en Discord.

¡Sé valiente y No te rindas!

Ilustracion que representa como crece alguien profesionalmente

Entérate de las últimas novedades

Streamings, Noticias y Early Adopter bonus. Sé el primero en enterarte de todo.