¿Porqué el mapeo de una matriz construida en JavaScript no funciona?

¿Porqué razón no funciona el mapeo de una matriz construida en JavaScript?

Escenario

Por el bien de la demostración, supongamos que necesitas generar una matriz de números del 0 al 99. ¿Cómo podría hacer esto? Aquí hay una opción:

const arr = [];

for (let i = 0; i < 100; i++) { arr[i] = i; }

Si eres como yo, ver un bucle tradicional en JavaScript te hace sentir un poco incómodo. De hecho, no he escrito un bucle tradicional en años gracias a las funciones de orden superior como  mapa, filtro y reducción. Tal vez no has tenido contacto con la programación funcional y estás pensando que la solución anterior parece estar perfectamente bien. Técnicamente lo es, pero después de haber probado la magia de las funciones de orden superior, probablemente estás pensando, «Debe haber una mejor manera». Mi primera reacción a este problema fue: «¡Lo sé! Crearé una matriz vacía de longitud 100 y mapearé los índices de cada elemento». JavaScript te permite crear una matriz vacía de longitud n con el constructor de matrices, de esta manera:


const arr = Array(100);

Perfecto, ¿verdad? Tengo una matriz de longitud 100, así que ahora sólo necesito mapear el índice de cada elemento.

const array = Array(100).map((_, i) => i);

console.log(arr[0] === undefined); // true

¿¡Qué ocurre!? El primer elemento del array debería ser 0, ¡pero en realidad es indefinido!

Explicación

Hay una distinción técnica importante que necesito hacer aquí para explicar por qué sucedió esto. Internamente, las matrices JavaScript son objetos, con números como claves. Por ejemplo:

['a', 'b', 'c']

es esencialmente equivalente a este objeto:

{
0: 'a',
1: 'b',
2: 'c',
length: 3
}

Cuando se accede al elemento en el índice 0 de un array, en realidad sólo se accede a una propiedad de objeto cuya clave es 0. Esto es importante porque cuando se piensa en los arrays como objetos en conjunción con la forma en que se implementan estas funciones de orden superior (no te preocupes, yo hice esa parte por ti), la causa de nuestro problema tiene sentido.

Cuando se crea una nueva matriz con el constructor de matrices, se crea un nuevo objeto de matriz con su propiedad de longitud ajustada al valor que tú has pasado, pero por lo demás el objeto es un vacío. No hay claves de índice en la representación de objetos del array.

{
//¡Nada de claves de índice!
length: 100
}

Se obtiene undefined cuando se intenta acceder al valor del array en el índice 0, pero no es que el valor indefinido se almacene en el índice 0, es que el comportamiento por defecto en JavaScript es devolver undefined si se intenta acceder al valor de un objeto para una clave que no existe.

Sucede que las funciones de orden superior mapean, reducen, filtran e iteran sobre las claves de índice del objeto array de 0 a longitud, pero la callback sólo se ejecuta si la clave existe en el objeto. Esto explica por qué nuestra callback nunca es llamada y no pasa nada cuando llamamos a la función de mapa en la matriz – ¡no hay claves de índice!

Solución

Como ya sabéis, lo que necesitamos es un array cuya representación interna de objetos contenga una clave para cada número desde el 0 hasta la longitud. La mejor manera de hacer esto es esparcir la matriz en una matriz vacía.

const arr = [...Array(100)].map((_, i) => i);

console.log(arr[0]);
// 0

La dispersión del array en un array vacío da como resultado un array que está lleno de elementos indefinidos en cada índice.

{
0: undefined,
1: undefined,
2: undefined,
...
99: undefined,
length: 100
}

Esto se debe a que el operador de extensión es más simple que la función de mapa. Simplemente recorre el array (o cualquier otro iterable, en realidad) de 0 a longitud y crea una nueva clave de índice en el array adjunto con el valor devuelto por el array de dispersión en el índice actual. Dado que JavaScript devuelve información no definida de nuestra matriz de dispersión en cada uno de sus índices (recuerda que lo hace por defecto porque no tiene la clave de índice para ese valor), terminamos con una nueva matriz que en realidad está llena de claves de índice y, por lo tanto, es mapeable (y reductible, filtrable y apta para cada uno).

Conclusión

Descubrimos algunas de las implicaciones de los arrays siendo representados internamente como objetos en Javascript, y aprendimos la mejor manera de crear arrays de longitud arbitraria llenos de cualquier valor que necesites.

Como siempre, puedes seguir navegando en esta web donde hallarás los mejores cursos en JavaScript.