hand Inicio

Cómo copiar un array en JavaScript?

Te explico cómo copiar arrays en JavaScript y por qué no es tan sencillo como parece

Todo por referencia (o era valor?)

Al igual que vimos con objects en cómo copiar un object, clonar arrays no es trivial ya que en JavaScript todo se copia por referencia

Qué es copiar por referencia?

En JavaScript cuando se copia un valor que NO es primitivo, en lugar de copiar el contenido de ese valor se copia una referencia a ese contenido

El símil con edificios sería que copiar por valor sería construir un edificio igual al que queremos copiar, y por lo tanto terminaríamos con 2 edificios iguales, mientras que copiar por referencia sería simplemente copiar la dirección, por lo que tendríamos 2 papeles con la dirección pero un único edificio

Pero qué es un valor primitivo?

A efectos prácticos, es un string, un number o un boolean

Es decir

  • cuando copiamos un string, un number o un boolean estamos copiando el valor
  • pero cuando copiamos por ejemplo un array estamos copiando la dirección de ese array, la referencia

Dicho esto, cuando se quiere clonar una variable lo que se quiere es hacer una copia por valor y no por referencia

Y no es trivial

*si quieres entretenerte, aquí tienes la pregunta del millón en stack overflow

Una manera práctica de verlo es con el uso de const

const y let son los sustitutos de var en ES6 (la versión "moderna" de JavaScript)

Cualquier variable que definimos con const será una constante, no podrá cambiarse nunca jamás

Por ejemplo, esto de aquí no podemos hacerlo

js
const mistring = 'hola'
mistring = 'hola otra vez' // ERROR, la variable mistring ya ha sido definida y es una constante!!

Esto falla porque estamos asignando un nuevo valor a la misma variable que ya se ha definido antes como constante

Con un array tenemos lo mismo

js
const miarray = ['hola']
miarray = ['hola otra vez'] // ERROR, la variable miarray ya ha sido definida y es una constante!!

Estamos asignando un array a una variable que ya ha sido definida, y no nos deja

Sin embargo, esto sí que podemos hacerlo

js
const miarray = []
miarray[0] = 'hola'
miarray[0] = 'hola otra vez' // ningún error

console.log(miarray) // ["hola otra vez"]

Y esto podemos hacerlo porque cuando utilizamos miarray[0] estamos accediendo a la primera posición del array, y esa posición no es una constante

Y podemos acceder de este modo porque miarray almacena una referencia y no un valor

Entender esto es importante para ver por qué copiar un array no es sencillo, y es porque una cosa es copiar valores y otra cosa es copiar referencias

Cuál es el problema con clonar arrays?

Con lo dicho arriba, puedes ver que el problema en copiar un array es que estás copiando una referencia y no un valor

Y cuando copias una referencia, pasan cosas como ésta:

js
const miarray = ['Me caes muy bien', 'Filomena']

const miarray2 = miarray
miarray2[0] = 'Me caes fatal'
miarray2[1] = 'Eustakio'

console.log(miarray) // ["Me caes fatal", "Eustakio"]
console.log(miarray2) // ["Me caes fatal", "Eustakio"]

Lo suyo hubiera sido tener dos copias del array con contenido distinto e independiente, pero vemos que cuando cambiamos el segundo array también estamos cambiando el primero, y no es lo que queremos

O en otras palabras, en este ejemplo mi intento de clonar un array ha fallado estrepitosamente

Copiando el array, copia simple

La copia simple simplemente copia la referencia del array, es lo que hemos visto arriba y no nos sirve si lo que queremos es clonar ese array

js
const myArr = ['😎😎😎😎']

const myCopy = myArr
myArr[0] = '🔥🔥🔥🔥'

console.log(myArr) // ["🔥🔥🔥🔥"]
console.log(myCopy) // ["🔥🔥🔥🔥"]

Copia shallow

Una copia shallow de arrays se refiere a copiar la primera "capa" o la primera dimensión del array

Se puede hacer con un Array.from() o con el spread operator, que lo que hace es "desplegarnos" la estructura de datos

js
const myCopy = myArr // copia simple
const myShallowCopy = Array.from(myArr) // copia shallow, con el Array.from
const myShallowCopy2 = [...myArr] // copia shallow, con el spread operator

Y para ver si estos métodos funcionan o no, hago lo mismo que antes, cambio los valores del array original y miro si los de la copia han cambiado o se mantienen independientes

js
const myArr = [
'😎😎😎😎', // una dimensión
'😎😎😎😎', // una dimensión
'😎😎😎😎', // una dimensión
['🦄🦄🦄🦄', '🌛🌛🌛🌛'], // dos dimensiones
{ myOtherArray: ['🤘🏾🤘🏾🤘🏾🤘🏾'] }, // tres dimensiones
]

const myCopy = myArr // copia simple
const myShallowCopy = Array.from(myArr) // copia shallow, con el Array.from
const myShallowCopy2 = [...myArr] // copia shallow, con el spread operator

// ahora cambio las variables de mi array inicial y así vemos cómo de independiente es mi copia

myArr[0] = '🔥🔥🔥🔥'
myArr[1] = '🔥🔥🔥🔥'
myArr[2] = '🔥🔥🔥🔥'
myArr[3][0] = '🔥🔥🔥🔥'
myArr[3][1] = '🔥🔥🔥🔥'
myArr[4].myOtherArray[0] = '🔥🔥🔥🔥'

// el array original y la copia simple, son idénticas

console.log(myArr) // ["🔥🔥🔥🔥", "🔥🔥🔥🔥", "🔥🔥🔥🔥", ["🔥🔥🔥🔥", "🔥🔥🔥🔥"], {myOtherArray: ["🔥🔥🔥🔥"]}]
console.log(myCopy) // ["🔥🔥🔥🔥", "🔥🔥🔥🔥", "🔥🔥🔥🔥", ["🔥🔥🔥🔥", "🔥🔥🔥🔥"], {myOtherArray: ["🔥🔥🔥🔥"]}]

// Las copias shallow, ves que ha protegido la primera dimensión del array, pero no las demás

console.log(myArr) // ["🔥🔥🔥🔥", "🔥🔥🔥🔥", "🔥🔥🔥🔥", ["🔥🔥🔥🔥", "🔥🔥🔥🔥"], {myOtherArray: ["🔥🔥🔥🔥"]}]
console.log(myShallowCopy) // ["😎😎😎😎", "😎😎😎😎", "😎😎😎😎", ["🔥🔥🔥🔥", "🔥🔥🔥🔥"], {myOtherArray: ["🔥🔥🔥🔥"]}]
console.log(myShallowCopy2) // ["😎😎😎😎", "😎😎😎😎", "😎😎😎😎", ["🔥🔥🔥🔥", "🔥🔥🔥🔥"], {myOtherArray: ["🔥🔥🔥🔥"]}]

Es decir, myArr y myShallowCopy o myShallowCopy2 son independientes en la primera dimensión, pero en las demás son el mismo objeto, con lo que para esas dimensiones si cambio el original también cambio la copia

Copia deep

Si una copia shallow era "superficial", una copia deep busca hacer una copia completa de todo el array, una copia de todas sus dimensiones

Y para hacerlo tenemos distintas maneras:

  • De forma manual con un loop que nos itere por todos los lados

  • Utilizando alguna librería ultra-famosa como lodash

Vamos a ver los dos métodos, el primero (el manual) utilizando la solución de samanthaming, y el segundo con la librería lodash

(si vas al codesandbox tendrás que añadir la dependencia de lodash)

js
// importo lodash
import _ from 'lodash'

// función manual para clonar arrays
const clone = items => items.map(item => (Array.isArray(item) ? clone(item) : item))

// el array original
const myArr = ['😎😎😎😎', ['😎😎😎😎'], { myOtherArray: ['😎😎😎😎'] }]

// copia shallow
const myShallowCopy = [...myArr]

// copia deep manual
const myDeepCopy = clone(myArr)

// copia deep con los dos métodos de lodash, uno para shallow y otro para deep
const myShallowLodashClone = _.clone(myArr)
const myDeepLodashClone = _.cloneDeep(myArr)

// cambio el array original
myArr[0] = '🔥🔥🔥🔥' // 1 dimensión
myArr[1][0] = '🔥🔥🔥🔥' // 2 dimensiones
myArr[2].myOtherArray[0] = '🔥🔥🔥🔥' // 3 dimensiones

// Y a ver qué nos dan los clones

console.log(myArr) // ["🔥🔥🔥🔥", ["🔥🔥🔥🔥"], {myOtherArray: ["🔥🔥🔥🔥"]}]
console.log(myShallowCopy) // ["😎😎😎😎", ["🔥🔥🔥🔥"], {myOtherArray: ["🔥🔥🔥🔥"]}]
console.log(myDeepCopy) // ["😎😎😎😎", ["😎😎😎😎"], {myOtherArray: ["🔥🔥🔥🔥"]}]
console.log(myShallowLodashClone) // ["😎😎😎😎", ["🔥🔥🔥🔥"], {myOtherArray: ["🔥🔥🔥🔥"]}]
console.log(myDeepLodashClone) // ["😎😎😎😎", ["😎😎😎😎"], {myOtherArray: ["😎😎😎😎"]}]
  • Las copias shallow sólo copian la primera dimensión, como hemos visto antes
  • La copia deep con la función propia no consigue copiar todas las dimensiones
  • Y una copia completa sí la conseguimos con _.cloneDeep de lodash

Podríamos modificar la función para conseguir los mismos resultados de lodash, de hecho la tienes en lodash.clonedeep, por lo que no tienes que importar toda la librería

O directamente puedes ir al github, ver la función, y utilizarla tu mismo

Copia deep casi completa con JSON

Hay otro método tremendamente práctico y que no requiere de librerías externas

Es el método JSON, y se trata de convertir nuestro array en un objeto JSON, y luego volver a convertir ese objeto en un nuevo array

En el proceso las referencias se consolidan en nuevos objetos, con lo que aunque la función JSON no está pensada para clonar datos, nos sirve perfectamente a este propósito

js
// el array original
const myArr = ['😎😎😎😎', ['🐥🐥🐥🐥'], { myOtherArray: ['🦄🦄🦄🦄'] }]

// la copia con JSON
const myJSONCopy = JSON.parse(JSON.stringify(myArr))

// cambio el array original
myArr[0] = '🔥🔥🔥🔥' // 1 dimensión
myArr[1][0] = '🔥🔥🔥🔥' // 2 dimensiones
myArr[2].myOtherArray[0] = '🔥🔥🔥🔥' // 3 dimensiones

// Y a ver qué nos da

console.log(myArr) // ["🔥🔥🔥🔥", ["🔥🔥🔥🔥"], {myOtherArray: ["🔥🔥🔥🔥"]}]
console.log(myJSONCopy) // ["😎😎😎😎", ["🐥🐥🐥🐥"], {myOtherArray: ["🦄🦄🦄🦄"]}]

Conseguimos clonar todo el objeto como hacíamos antes con el _.cloneDeep de lodash

  • Ventajas? Se lee muy fácilmente y no dependes de una librería externa
  • Desventajas? Cierta incompatibilidad con algunos tipos de datos que no se pueden clonar bien (como Date)

En cuanto a velocidad, haciendo un benchmark con measurethat.net vemos lo siguiente:

  • Con estructuras sencillas, la solución con lodash es un 1.5x más rápida que la solución con JSON
  • Con estructuras "algo" complejas como la que verás debajo, la solución JSON ya empieza a ir más rápido que la de lodash
js
const myArr = [
'😎😎😎😎',
['🐥🐥🐥🐥'],
{ myOtherArray: ['🦄🦄🦄🦄'] },
'😎😎😎😎',
['🐥🐥🐥🐥'],
{ myOtherArray: ['🦄🦄🦄🦄'] },
'😎😎😎😎',
['🐥🐥🐥🐥'],
{ myOtherArray: ['🦄🦄🦄🦄'] },
'😎😎😎😎',
['🐥🐥🐥🐥'],
{ myOtherArray: ['🦄🦄🦄🦄'] },
'😎😎😎😎',
['🐥🐥🐥🐥'],
{ myOtherArray: ['🦄🦄🦄🦄'] },
'😎😎😎😎',
['🐥🐥🐥🐥'],
{ myOtherArray: ['🦄🦄🦄🦄'] },
'😎😎😎😎',
['🐥🐥🐥🐥'],
{ myOtherArray: ['🦄🦄🦄🦄'] },
'😎😎😎😎',
['🐥🐥🐥🐥'],
{ myOtherArray: ['🦄🦄🦄🦄'] },
'😎😎😎😎',
['🐥🐥🐥🐥'],
{ myOtherArray: ['🦄🦄🦄🦄'] },
]

Con este array, la solución más rápida sería la de JSON

Eso sí, con ninguno de estos métodos conseguiríamos clonar un array que tenga una función en su estructura, lo puedes ver abajo

js
const myArr = [
'😎😎😎😎',
['🐥🐥🐥🐥'],
{ myOtherArray: ['🦄🦄🦄🦄'], myMethod: () => console.log('hola como estamos') },
]

Este método tiene una función en la estructura, pues escojas el método que escojas, esa función no la podrás clonar

🙋‍♂️

draw of me

Hola, tienes en mente desarrollar una web?

Si quieres, te ayudo

Escríbeme