Consejos útiles para crear y clonar matrices en JavaScript.

Un aspecto muy importante de cada lenguaje de programación son los tipos de datos y las estructuras disponibles en el lenguaje. La mayoría de los lenguajes de programación proporcionan tipos de datos para representar y trabajar con datos complejos. Si ha trabajado con lenguajes como Python o Ruby, debería haber visto tipos de datos como listas , conjuntos , tuplas , hashes , dictados , etc.
En JavaScript, no hay tantos tipos de datos complejos, simplemente tiene matrices y objetos . Sin embargo, en ES6, se agregaron al lenguaje un par de tipos de datos y estructuras, como símbolos , conjuntos y mapas .
Las matrices en JavaScript son objetos similares a listas de alto nivel con una propiedad de longitud y propiedades enteras como índices.En este artículo, comparto un par de trucos para crear nuevas matrices de JavaScript o clonar las ya existentes.
Creación de matrices: el constructor de matrices
El método más popular para crear matrices es utilizar la sintaxis literal de matriz , que es muy sencilla. Sin embargo, cuando desee crear matrices de forma dinámica, es posible que la sintaxis literal de matriz no sea siempre el mejor método. Un método alternativo es utilizar el Array
constructor.
Aquí hay un fragmento de código simple que muestra el uso del Array
constructor.
En el fragmento anterior, podemos ver que el Array
constructor crea matrices de manera diferente según los argumentos que recibe.
Nuevas matrices: con longitud definida
Veamos más de cerca lo que sucede al crear un nuevo Array
de una longitud determinada. El constructor simplemente establece la length
propiedad de la matriz a la longitud dada, sin establecer las claves.

A partir del fragmento anterior, puede tener la tentación de pensar que cada clave de la matriz se estableció en un valor de undefined
. Pero la realidad es que esas claves nunca se establecieron (no existen).
La siguiente ilustración lo aclara:

Esto hace que sea inútil intentar utilizar cualquiera de los métodos de iteración de la matriz como map()
, filter()
o reduce()
manipular la matriz. Digamos que queremos llenar cada índice de la matriz con el número 5
como valor. Intentaremos lo siguiente:

Podemos ver que map()
no funcionó aquí, porque las propiedades del índice no existen en la matriz, solo length
existe la propiedad.
Veamos diferentes formas en las que podemos solucionar este problema.
1. Usando Array.prototype.fill ()
El fill()
método llena todos los elementos de una matriz desde un índice inicial hasta un índice final con un valor estático. El índice final no está incluido. Puede obtener más información fill()
aquí.
Tenga en cuenta que fill()
solo funcionará en navegadores compatibles con ES6.
Aquí hay una ilustración simple:

Aquí, hemos podido llenar todos los elementos de nuestra matriz creada con 5
. Puede establecer cualquier valor estático para diferentes índices de la matriz utilizando el fill()
método.
2. Usando Array.from ()
El Array.from()
método crea una nueva Array
instancia de copia superficial a partir de un objeto iterable o similar a una matriz. Puede obtener más información Array.from()
aquí.
Tenga en cuenta que Array.from()
solo funcionará en navegadores compatibles con ES6.
Aquí hay una ilustración simple:

Aquí, ahora tenemos undefined
valores verdaderos establecidos para cada elemento de la matriz que usa Array.from()
. Esto significa que ahora podemos seguir adelante y usar métodos como .map()
y .filter()
en la matriz, ya que las propiedades del índice ahora existen.
Una cosa más que vale la pena señalar Array.from()
es que puede tomar un segundo argumento, que es una función de mapa. Se llamará en cada elemento de la matriz. Esto hace que sea redundante llamar .map()
después Array.from()
.
Aquí hay un ejemplo simple:

3. Uso del operador de propagación
El operador de propagación( ...
), agregado en ES6, se puede usar para distribuir los elementos de la matriz, estableciendo los elementos faltantes en un valor de undefined
. Esto producirá el mismo resultado que simplemente llamar Array.from()
con solo la matriz como único argumento.
Aquí hay una ilustración simple del uso del operador de propagación:

Puede seguir adelante y usar métodos como .map()
y .filter()
en la matriz, ya que las propiedades del índice ahora existen.
Usando Array.of ()
Al igual que vimos con la creación de nuevas matrices utilizando el Array
constructor o la función, se Array.of()
comporta de manera muy similar. De hecho, la única diferencia entre Array.of()
y Array
está en cómo manejan un único argumento entero que se les pasa.
Mientras Array.of(5)
crea una nueva matriz con un solo elemento, 5
y una propiedad de longitud de 1
, Array(5)
crea una nueva matriz vacía con 5 ranuras vacías y una propiedad de longitud de 5
.
var array1 = Array.of(5); // [5] var array2 = Array(5); // Array(5) {length: 5}
Además de esta gran diferencia, se Array.of()
comporta como el Array
constructor. Puede obtener más información Array.of()
aquí.
Tenga en cuenta que Array.of()
solo funcionará en navegadores compatibles con ES6.
Conversión a matrices: Me gusta de matrices e iterables
Si ha estado escribiendo funciones JavaScript el tiempo suficiente, ya debería conocer el arguments
objeto, que es un objeto similar a una matriz disponible en cada función para contener la lista de argumentos que recibió la función. Aunque el arguments
objeto se parece mucho a una matriz, no tiene acceso a los Array.prototype
métodos.
Antes de ES6, normalmente vería un fragmento de código como el siguiente al intentar convertir el arguments
objeto en una matriz:

Con Array.from()
o el operador de extensión, puede convertir convenientemente cualquier objeto similar a una matriz en una matriz. Por lo tanto, en lugar de hacer esto:
var args = Array.prototype.slice.call(arguments);
puede hacer cualquiera de estos:
// Using Array.from() var args = Array.from(arguments); // Using the Spread operator var args = [...arguments];
Estos también se aplican a los iterables como se muestra en la siguiente ilustración:

Estudio de caso: función de rango
Como caso de estudio antes de continuar, crearemos una range()
función simple para implementar el nuevo truco de matriz que acabamos de aprender. La función tiene la siguiente firma:
range(start: number, end: number, step: number) => Array
Aquí está el fragmento de código:
En este fragmento de código, usamos Array.from()
para crear la nueva matriz de rango de longitud dinámica y luego completarla con números incrementados secuencialmente proporcionando una función de mapeo.
Tenga en cuenta que el fragmento de código anterior no funcionará para navegadores sin compatibilidad con ES6, excepto si usa polyfills.
A continuación, se muestran algunos resultados de llamar a la range()
función definida en el fragmento de código anterior:

Puede obtener una demostración de código en vivo ejecutando el siguiente lápiz en Codepen :
Clonación de matrices: el desafío
En JavaScript, las matrices y los objetos son tipos de referencia. Esto significa que cuando a una variable se le asigna una matriz u objeto, lo que se asigna a la variable es una referencia a la ubicación en la memoria donde se almacenó la matriz u objeto.
Las matrices, al igual que cualquier otro objeto en JavaScript, son tipos de referencia. Esto significa que las matrices se copian por referencia y no por valor.
El almacenamiento de tipos de referencia de esta manera tiene las siguientes consecuencias:
1. Las matrices similares no son iguales.

Aquí, vemos que aunque array1
y array2
aparentemente contienen las mismas especificaciones de matriz, no son iguales. Esto se debe a que la referencia a cada una de las matrices apunta a una ubicación diferente en la memoria.
2. Las matrices se copian por referencia y no por valor.

Aquí, intentamos copiar array1
a array2
, pero lo que básicamente estamos haciendo es apuntar array2
a la misma ubicación en la memoria que array1
apunta. Por lo tanto, ambos array1
y array2
apuntan a la misma ubicación en la memoria y son iguales.
La implicación de esto es que cuando hacemos un cambio al array2
eliminar el último elemento, array1
también se elimina el último elemento de . Esto se debe a que el cambio se realizó en realidad en la matriz almacenada en la memoria, mientras que array1
y array2
son solo punteros a la misma ubicación en la memoria donde se almacena la matriz.
Matrices de clonación: los trucos
1. Usando Array.prototype.slice ()
El slice()
método crea una copia superficial de una parte de una matriz sin modificar la matriz. Puede obtener más información slice()
aquí.
El truco consiste en llamar slice()
con 0
el único argumento o sin ningún argumento:
// with O as only argument array.slice(0); // without argument array.slice();
Aquí hay una ilustración simple de la clonación de una matriz con slice()
:

Aquí, puede ver que array2
es un clon de array1
con los mismos elementos y longitud. Sin embargo, apuntan a diferentes ubicaciones en la memoria y, como resultado, no son iguales. También nota que cuando hacemos un cambio al array2
eliminar el último elemento, array1
permanece sin cambios.
2. Usando Array.prototype.concat ()
El concat()
método se utiliza para fusionar dos o más matrices, lo que da como resultado una nueva matriz, mientras que las matrices originales no se modifican. Puede obtener más información concat()
aquí.
El truco consiste en llamar concat()
con una matriz vacía ( []
) como argumento o sin ningún argumento:
// with an empty array array.concat([]); // without argument array.concat();
Clonar una matriz con concat()
es bastante similar a usar slice()
. Aquí hay una ilustración simple de la clonación de una matriz con concat()
:

3. Usando Array.from ()
Como vimos anteriormente, Array.from()
se puede usar para crear una nueva matriz que es una copia superficial de la matriz original. Aquí hay una ilustración simple:

4. Uso de la desestructuración de matrices
Con ES6, tenemos algunas herramientas más poderosas en nuestra caja de herramientas, como desestructurar , extenderoperador , funciones de flecha , etc. La desestructuración es una herramienta muy poderosa para extraer datos de tipos complejos como matrices y objetos.
El truco consiste en utilizar una técnica llamada parámetros de descanso, que implica una combinación de desestructuración de matrices y el operador de propagación, como se muestra en el siguiente fragmento:
let [...arrayClone] = originalArray;
El fragmento de código anterior crea una variable denominada arrayClone
que es un clon de originalArray
. Aquí hay una ilustración simple de la clonación de una matriz usando la desestructuración de matriz:

Clonación: superficial versus profunda
Todas las técnicas de clonación de matrices que hemos explorado hasta ahora producen una copia superficial de la matriz. Esto no será un problema si la matriz contiene solo valores primitivos. Sin embargo, si la matriz contiene referencias de objetos anidados, esas referencias permanecerán intactas incluso cuando se clone la matriz.
Aquí hay una demostración muy simple de esto:

Tenga en cuenta que la modificación de la matriz anidada array1
también modificó la matriz anidada en array2
y viceversa.
La solución a este problema es crear una copia profunda de la matriz y hay un par de formas de hacerlo.
1. La técnica JSON
La forma más sencilla de crear una copia profunda de una matriz es mediante una combinación de JSON.stringify()
y JSON.parse()
.
JSON.stringify()
convierte un valor JavaScript en una cadena JSON válida, mientras que JSON.parse()
convierte una cadena JSON en un valor u objeto JavaScript correspondiente.
Aquí hay un ejemplo simple:

Estas fallas en la técnica JSON se pueden atribuir principalmente a la forma en que el JSON.stringify()
método convierte los valores en cadenas JSON.
Aquí hay una demostración simple de esta falla al intentar JSON.stringify()
un valor que contiene una función anidada.

2. Asistente de copia profunda
Una alternativa viable a la técnica JSON será implementar su propia función de ayuda de copia profunda para clonar tipos de referencia, ya sean matrices u objetos.
Aquí hay una función de copia profunda muy simple y minimalista llamada deepClone
:
Ahora bien, esta no es la mejor de las funciones de copia profunda que existen, como pronto verá con algunas bibliotecas de JavaScript; sin embargo, realiza una copia profunda en bastante buena medida.

3. Uso de bibliotecas de JavaScript
La función de ayuda de copia profunda que acabamos de definir no es lo suficientemente sólida para clonar todos los tipos de datos de JavaScript que pueden estar anidados dentro de objetos o matrices complejos.
Las bibliotecas de JavaScript como Lodash y jQuery proporcionan funciones de utilidad de copia profunda más sólidas con soporte para diferentes tipos de datos de JavaScript.
Aquí hay un ejemplo que utiliza _.cloneDeep()
de la biblioteca Lodash:

Aquí está el mismo ejemplo pero usando $.extend()
de la biblioteca jQuery:

Conclusión
En este artículo, hemos podido explorar varias técnicas para crear dinámicamente nuevas matrices y clonar las ya existentes, incluida la conversión de iterables y objetos similares a matrices en matrices.
También hemos visto cómo algunas de las nuevas funciones y mejoras introducidas en ES6 pueden permitirnos realizar de manera efectiva ciertas manipulaciones en arreglos.
Usamos características como la desestructuración y el operador de propagación para clonar y propagar matrices. Puede obtener más información sobre la desestructuración en este artículo.
Aplaudir y seguir
Si este artículo le resultó revelador, puede dar algunos aplausos si no le importa.
También puede seguirme en Medium (Glad Chinda) para obtener artículos más interesantes que pueden resultarle útiles. También puedes seguirme en Twitter (@gladchinda).
Feliz piratería ...