Colecciones indexadas

En JavaScript, las colecciones indexadas son estructuras de datos que almacenan elementos y permiten acceder a ellos mediante un índice numérico.


Arrays o vectores

Pero a menudo necesitamos una colección ordenada, donde tenemos un 1ro, un 2do, un 3er elemento y así sucesivamente. Por ejemplo, necesitamos almacenar una lista de algo: usuarios, bienes, elementos HTML, etc.

No es conveniente usar objetos aquí, porque no proveen métodos para manejar el orden de los elementos. No podemos insertar una nueva propiedad “entre” los existentes. Los objetos no están hechos para eso.

Existe una estructura llamada Array (llamada en español arreglo o matriz/vector) para almacenar colecciones ordenadas.

Array

En JavaScript, los arrays no son primitivas, sino objetos Array con las siguientes características básicas:

Índices

Los objetos array no pueden utilizar cadenas arbitrarias como índices de elementos (como en un array asociativo), sino que deben utilizar enteros no negativos (o su respectiva forma de cadena). El establecimiento o acceso a través de números no enteros no establecerá o recuperará un elemento de la propia lista de la matriz, sino que establecerá o accederá a una variable asociada con la colección de propiedades de objeto de esa matriz. Las propiedades de objeto de la matriz y la lista de elementos de la matriz son independientes, y las operaciones de recorrido y mutación de la matriz no se pueden aplicar a estas propiedades con nombre.

Error

console.log(arr.0); // a syntax error

La sintaxis de JavaScript requiere que se acceda a las propiedades que empiezan por un dígito utilizando la notación de corchetes en lugar de la notación de puntos. También es posible entrecomillar los índices de la matriz (por ejemplo, years['2'] en lugar de years[2]), aunque no suele ser necesario.

El motor JavaScript convierte el 2 de years[2] en una cadena mediante una conversión implícita toString. Como resultado, '2' y '02' se referirían a dos ranuras diferentes en el objeto years, y el siguiente ejemplo podría ser true:

Correcto

console.log(years["2"] !== years["02"]);

Sólo years['2'] es un índice real del array. years['02'] es una propiedad de cadena arbitraria que no será visitada en la iteración del array.

Relación entre la longitud y las propiedades numéricas

La propiedad de longitud de un array JavaScript y las propiedades numéricas están conectadas.

Varios de los métodos de array incorporados (por ejemplo, join(), slice(), indexOf(), etc.) tienen en cuenta el valor de la propiedad length de un array cuando se llaman.

Otros métodos (por ejemplo, push(), splice(), etc.) también resultan en actualizaciones de la propiedad length de un array.

const fruits = [];
fruits.push("banana", "apple", "peach");
console.log(fruits.length); // 3

Al establecer una propiedad en una matriz JavaScript, cuando la propiedad es un índice de matriz válido y ese índice está fuera de los límites actuales de la matriz, el motor actualizará la propiedad de longitud de la matriz en consecuencia:

fruits[5] = "mango";
console.log(fruits[5]); // 'mango'
console.log(Object.keys(fruits)); // ['0', '1', '2', '5']
console.log(fruits.length); // 6

Aumentar la longitud.

fruits.length = 10;
console.log(fruits); // ['banana', 'apple', 'peach', empty x 2, 'mango', empty x 4]
console.log(Object.keys(fruits)); // ['0', '1', '2', '5']
console.log(fruits.length); // 10
console.log(fruits[8]); // undefined

Sin embargo, al reducir la propiedad de longitud se eliminan elementos.

fruits.length = 2;
console.log(Object.keys(fruits)); // ['0', '1']
console.log(fruits.length); // 2

Métodos de matrices y ranuras vacías

Las ranuras vacías en matrices dispersas se comportan de forma inconsistente entre los métodos de matrices. Por lo general, los métodos más antiguos omiten las ranuras vacías, mientras que los más recientes las tratan como undefined.

Métodos de copia y métodos de mutación

Algunos métodos no mutan el array existente sobre el que se ha llamado al método, sino que devuelven un nuevo array. Lo hacen accediendo primero a this.constructor[Symbol.species] para determinar el constructor a utilizar para el nuevo array. A continuación, la nueva matriz se rellena con elementos. La copia siempre se realiza de forma superficial: el método nunca copia nada más allá de la matriz creada inicialmente. Los elementos de la(s) matriz(es) original(es) se copian en la nueva matriz de la siguiente manera:

Otros métodos mutan el array sobre el que se ha llamado al método, en cuyo caso su valor de retorno difiere según el método: a veces una referencia al mismo array, a veces la longitud del nuevo array.

Métodos iterativos

Muchos métodos de matrices toman como argumento una función callback. La función callback se llama secuencialmente y como máximo una vez por cada elemento del array, y el valor de retorno de la función callback se utiliza para determinar el valor de retorno del método. Todos comparten la misma firma:

method(callbackFn, thisArg)

Donde callbackFn toma tres argumentos:

Lo que se espera que devuelva callbackFn depende del método de array que fue llamado.

El argumento thisArg (por defecto undefined) se utilizará como el valor this cuando se llame a callbackFn. El valor this observable en última instancia por callbackFn se determina según las reglas habituales: si callbackFn no es estricto, los valores this primitivos se envuelven en objetos, y undefined/null se sustituye por globalThis. El argumento thisArg es irrelevante para cualquier callbackFn definido con una función de flecha, ya que las funciones de flecha no tienen su propio enlace this.

Todos los métodos iterativos son copiadores y genéricos, aunque se comportan de forma diferente con ranuras vacías.

Métodos de array genéricos

Los métodos de array son siempre genéricos - no acceden a ningún dato interno del objeto array. Sólo acceden a los elementos del array a través de la propiedad length y a los elementos indexados. Esto significa que también pueden invocarse en objetos tipo array.

const arrayLike = {
  0: "a",
  1: "b",
  length: 2,
};
console.log(Array.prototype.join.call(arrayLike, "+")); // 'a+b'

Normalización de la propiedad de longitud

La propiedad length se convierte a un entero y luego se sujeta al rango entre 0 y 25312^{53} - 1. NaN se convierte en 0, por lo que incluso cuando length no está presente o es undefined, se comporta como si tuviera valor 0.

Array.prototype.flat.call({}); // []

Algunos métodos de array establecen la propiedad length del objeto array. Siempre establecen el valor después de la normalización, por lo que la longitud siempre termina como un entero.

const a = { length: 0.7 };
Array.prototype.push.call(a);
console.log(a.length); // 0

Objetos array-like

El término objeto tipo array se refiere a cualquier objeto que no se lance durante el proceso de conversión de longitud descrito anteriormente. En la práctica, se espera que dicho objeto tenga realmente una propiedad length y que tenga elementos indexados en el rango de 0 a length1length - 1. (Si no tiene todos los índices, será funcionalmente equivalente a un array disperso).

Muchos objetos DOM son del tipo array - por ejemplo, NodeList y HTMLCollection. El objeto arguments también es un array-like. Puedes llamar a métodos de array en ellos incluso si ellos mismos no tienen estos métodos.

function f() {
  console.log(Array.prototype.join.call(arguments, "+"));
}
 
f("a", "b"); // 'a+b'